首页 > 其他分享 >线段树

线段树

时间:2023-09-11 22:23:02浏览次数:25  
标签:int 线段 long mu add sum mod

线段树,一种非常通用的数据结构,多用于区间查询问题,虽然在时间和空间效率上都不如树状数组,但是因为其维护和操作更简单而受oier青睐

为了加深记忆 特此写篇博客 大佬轻喷

线段树,是一颗完全二叉树,由上到下维护,支持询问,更改等多种操作变种包括可持久化线段树及若干,本篇博客只提最简单的普通线段树,支持区间加法,乘法和询问

 线段树如图所示

由此可得代码


 

 

#include<bits/stdc++.h> using namespace std; const int N=4e5+5; struct Segmentree{     long long l,r,add,sum,mu;     #define l(x) tree[x].l     #define r(x) tree[x].r     #define add(x) tree[x].add     #define sum(x) tree[x].sum     #define mu(x) tree[x].mu }tree[N]; long long n,m,mod,a[N]; void build(int p,int l,int r) {     l(p)=l,r(p)=r,mu(p)=1;     //此处mu初始化,乘法标记初始必须为1     if(l==r){sum(p)=a[l]%mod;return;}     long long mid=(l+r)>>1;     build(p*2,l,mid);     build(p*2+1,mid+1,r);     sum(p)=(sum(p*2)+sum(p*2+1))%mod; }//建树,没有差别,记得取模 void spread(int p) {     //sum数组处理     //欲下传的乘法标记乘以下传处的总和    同时处理加法标记的问题 欲下传的加法标记乘以区间长度     //左右区间分别处理     sum(p*2)=(long long)(mu(p)*sum(p*2)+(r(p*2)-l(p*2)+1)*add(p)%mod)%mod;     sum(p*2+1)=(long long)(mu(p)*sum(p*2+1)+((r(p*2+1)-l(p*2+1)+1)*add(p))%mod)%mod;     //乘法标记处理     //当前乘法标记乘以左(或右)儿子的乘法标记     mu(p*2)=(long long)(mu(p*2)*mu(p))%mod;     mu(p*2+1)=(long long)(mu(p*2+1)*mu(p))%mod;     //加法标记处理     //当前乘法标记乘以左(或右)儿子的乘法标记,再加上当前的加法标记     add(p*2)=(long long)(mu(p)*add(p*2)+add(p))%mod;     add(p*2+1)=(long long)(mu(p)*add(p*2+1)+add(p))%mod;         mu(p)=1,add(p)=0;//处理完初始化     //每步操作都要求取模 } void changeadd(long long p,long long l,long long r,long long k) {     //对区间加法处理     if(l(p)>=l&&r(p)<=r)     {         add(p)=(add(p)+k)%mod;         sum(p)=(long long)(sum(p)+k*(r(p)-l(p)+1))%mod;         return;     }     spread(p);//下传标记     sum(p)=(sum(p*2)+sum(p*2+1))%mod;     long long mid=(l(p)+r(p))>>1;     if(l<=mid) changeadd(p*2,l,r,k);     if(r>mid) changeadd(p*2+1,l,r,k);     sum(p)=(sum(p*2)+sum(p*2+1))%mod;     } void changemul(long long p,long long l,long long r,long long k) {     if(l(p)>=l&&r(p)<=r)     {         add(p)=(add(p)*k)%mod;         //比较重要的一步,add要在这里乘上k,因为后面可能要加其他的数而那些数其实是不用乘k的         //只需谨记 先乘再加         mu(p)=(mu(p)*k)%mod;         sum(p)=(sum(p)*k)%mod;         return;     }     spread(p);//下传标记     sum(p)=(sum(p*2)+sum(p*2+1))%mod;     long long mid=(l(p)+r(p))>>1;     if(l<=mid) changemul(p*2,l,r,k);     if(r>mid) changemul(p*2+1,l,r,k);     sum(p)=(sum(p*2)+sum(p*2+1))%mod; } long long ask(int p,int l,int r) {     //找到了     //退出,返回初值     if(l(p)>=l&r(p)<=r){         return sum(p);     }     spread(p);//下传标记,调用下一层的值     long long val=0;     long long mid=(l(p)+r(p))>>1;     if(l<=mid)val=(val+ask(p*2,l,r))%mod;     if(mid<r)val=(val+ask(p*2+1,l,r))%mod;     return val; } int main() {     cin>>n>>m>>mod;     for(int i=1;i<=n;i++)         cin>>a[i];     build(1,1,n);//建树入口     for(int i=1;i<=m;i++)     {         int x;         cin>>x;         if(x==1)         {             long long a,b,c;             cin>>a>>b>>c;             changemul(1,a,b,c);         }         else if(x==2)         {             long long a,b,c;             cin>>a>>b>>c;             changeadd(1,a,b,c);         }         else{             long long a,b;             cin>>a>>b;             printf("%lld\n",ask(1,a,b));         }     }     return 0; }

 


 

标签:int,线段,long,mu,add,sum,mod
From: https://www.cnblogs.com/mingloch/p/17694698.html

相关文章

  • 线段树【区间求和】
    #include<bits/stdc++.h>#definemaxn500005usingnamespacestd;intn,m;inta[maxn];structnode{ intl,r,sum;};nodetr[4*maxn];voidbuild(intl,intr,intp){ //对[l,r]区间建立线段树,当前根的编号为p intmid=(l+r)>>1; //intmid=s+((t-s)&g......
  • 6574: 最大数 线段树/单点加/求区间最大值
    描述 给定一个正整数数列a1,a2,a3,⋯,an,每一个数都在0~p–1之间。可以对这列数进行两种操作:添加操作:向序列后添加一个数,序列长度变成n+1;询问操作:询问这个序列中最后L个数中最大的数是多少。程序运行的最开始,整数序列为空。写一个程序,读入操作的序列,并输出询问操作的......
  • 线段树的一种简单实现
    发现之前没有整理过线段树的代码,填一下坑。intArray[maxn];classSegmentTree{public:SegmentTree*BuildTree(constintL,constintR){SegmentTree*Node=newSegmentTree;Node->l=L,Node->r=R;Node->Tag=0;if(L==R){Node->LeftSon=Node-......
  • 线段树
    树状数组是个好东西,写起来也相对好看。但是操作比较局限,区间修改就掉回\(O(nlogn)\),那还不如\(O(n)\)。线段树完美的解决问题。线段树,也可以理解的一堆线段组成的树。大致如上,有一线段\([l,r]\)当\(l\ner\)可化为\([l,mid]\),\([mid+1,r]\)PS:\(mid=(l+r......
  • 线段树进阶
    普通线段树核心在于上传标记(pushup)和下传标记(pushdown)以及懒标记的设计。P3373【模板】线段树2维护一个加法标记和乘法标记。下传标记时,将乘法标记更新加法标记。标记下传实现voidpushdown(intu,intl,intr){intmid=l+r>>1;tr[u<<1].val=(tr[u<<1].val*tr[......
  • Codeforces Round 406 (Div. 2) D. Legacy 线段树优化建图
    传送门题目大意:给定n个点,m个操作,和起点s。其中n和q大于等于1小于等于1e5,s大于等于1小于等于n其中m个操作有三种情况:  1.输入1uvval表示从u号点向v号点连一个权值为val的有向边,其中1<=u<=n,1<=v<=n,1<=val<=1e9  2.输入2ulrval表示从u号点......
  • 吉司机线段树
    一、区间历史最值以区间历史最大值为例。首先,相应地,设\(maxb\)表示一个节点的区间历史最大值。为了更新一个区间的子区间,再设一个\(tag2\),表示\(tag1\)从上次\(push\_down\)以后到现在达到过的最大值。\(code:\)voidpush_up(intu){p[u].w=p[u<<1].w+p[u<<1|1].w......
  • 李超线段树学习笔记
    李超线段树学习笔记P4097【模板】李超线段树/[HEOI2013]Segment题意要求在平面直角坐标系下维护两个操作:在平面上加入一条线段。记第\(i\)条被插入的线段的标号为\(i\)。给定一个数\(k\),询问与直线\(x=k\)相交的线段中,交点纵坐标最大的线段的编号。做法首先,......
  • 线段树
    建树:inta[100005],d[100005];voidbuild(ints,inte,intp){//建树 //对区间[s,t]建立线段树,当前根编号为p if(s==e){ d[p]=a[s]; return; } intm=s+((e-s)>>1); build(s,m,p*2); build(m+1,e,p*2+1);//分割出两个子区间 d[p]=d[p*2]+d[(p*2)+1];}//d[i]为......
  • P3373 【模板】线段树 2
    【模板】线段树2如题,已知一个数列,你需要进行下面三种操作:将某区间每一个数乘上\(x\);将某区间每一个数加上\(x\);求出某区间每一个数的和。输入格式第一行包含三个整数\(n,q,m\),分别表示该数列数字的个数、操作的总个数和模数。第二行包含\(n\)个用空格分隔的整数,其......