zoukankan      html  css  js  c++  java
  • 线段树讲解 (一本通)

    RMQ问题:区间最大值或者最小值问题,类似的还要区间和问题

    操作:

    (1)求最值 :区间内

    (2)修改元素 :点修改

    线段树:用于区间处理的数据结构,用二叉树构造

    复杂度:O(nlogn):二叉折半查找

    查找点或者区间的时候:顺着往下查找

    修改元素:直接修改叶子节点,然后自底向上更新

    !!!存储空间:4n

    更新和查询:更新经过的每个节点,就把这个节点剩下的数量(长度)减1

    复杂度:线段是把n个数按照二叉树进行分组,每次更新有关节点的时候,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数

    例题:

    还是last cows

    第一种做法:用结构体实现线段树

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //线段树做法
    /*
    从后往前遍历输入的序列,遇到的每个值a表示此牛在剩余牛中排在第a+1个,删除此编号,循环此过程,最终得到的序列即为牛在此队列中的编号序列。
    借助线段树查找未删除的数中排在第a+1个位置(编号排序位置)的牛的位置(读取顺序)
    */
    struct node{
        int l,r,len;
    }cow[100000];
    int s[100000],ans[100000];
    void build(int v,int l,int r){
        cow[v].l=l;
        cow[v].r=r;
        cow[v].len=r-l+1;
        if(l==r) return;
        int mid=(l+r)/2;
        build(v*2,l,mid);
        build(v*2+1,mid+1,r);
    }
    int que(int v,int k){
        --cow[v].len;
        if(cow[v].l==cow[v].r) return cow[v].r;
        //找到叶子节点, 注意此处不可用cow[v].len == 0代替,否则单支情况将直接返回,导致未达到最末端
        else if(cow[v*2].len>=k){
            return que(v*2,k);
        }
        else return que(v*2+1,k-cow[v*2].len);////!!!!
    }
    int main(){
        int n;
        while(~scanf("%d",&n)){
            for(int i=2;i<=n;i++) scanf("%d",&s[i]);
            s[1]=0;
            build(1,1,n);
            for(int i=n;i>=1;i--){
                ans[i]=que(1,s[i]+1);
            }
            for(int i=1;i<=n;i++) printf("%d
    ",ans[i]);
        }
    return 0;
    }
    

    第二种做法:完全二叉树(数组)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=11010;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //数组实现线段树
    int n;
    int pre[maxn],tree[maxn*4]={0},ans[maxn]={0};
    void build(int n,int last_left){
    	for(int i=last_left;i<last_left+n;i++) tree[i]=1; //最后一行赋值
    	//从二叉树的最后一行倒推到根节点,根节点的值是牛的总数 
    	while(last_left!=1){
    		for(int i=last_left/2;i<last_left;i++) tree[i]=tree[i*2]+tree[i*2+1];
    		last_left/=2;
    	} 
    }
    
    int que(int u,int num,int last_left){  //查询+维护,求出当前区间中坐起第num个元素
    	tree[u]--;
    	if(tree[u]==0&&u>=last_left) return u;
    	if(tree[u<<1]<num)   //左子区间数量不够,查到右子区间 
    		return que((u<<1)+1,num-tree[u<<1],last_left); 
    	if(tree[u<<1]>=num)  //左子区间数量够了 
    		return que(u<<1,num,last_left);
    }
    int main(){
    	int las;
    	scanf("%d",&n);
    	pre[1]=0;
    	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
    	las=1<<(int(log(n)/log(2))+1);
    	//cout<<las<<endl;
    	build(n,las);  //从后往前退出每次最后一个数字 
    	for(int i=n;i>=1;i--) ans[i]=que(1,pre[i]+1,las)-las+1;
    	for(int i=1;i<=n;i++) printf("%d
    ",ans[i]); 
    return 0;
    }

    当数据太大:也可以考虑离散化,把原有的大二叉树压缩为小二叉树,但是压缩前后子区间的关系不变

    区间修改

    操作:(1)加 (2)查询和

    lazy_tag方法:当修改一个整块区间时,只对这个线段区间进行整体上的修改,其内部每个元素内容先不修改,只有当这部分线段的一致性被破坏时才把变化之传给子区间(查询时也一样)

    tag[]数组:记录节点i是否用到lazy原理,其值是op a b c中的c,如果做了多次lazy,那么add[]可以累加,如果在某次操作中被深入, 破坏了lazy,那么add[]归0

    POJ 3468  A Simple Problem with Integers

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    //有错 
    const int maxn=1e5+5;
    const int INF=0x3fffffff;
    typedef long long LL;
    LL summ[maxn<<2],add[maxn<<2];  //4倍空间 
    void pusup(int r){  //向上更新,把值给i递归到父节点  什么时候使用这个函数呢?当现在的节点发生变化,需要顺便改变父节点 
    	summ[r]=summ[r<<1]+summ[r<<1|1];
    }
    void push_down(int rt,int m){ //更新r的子节点,m为长度 
     	if(add[rt]){
     		add[rt<<1]+=add[rt];
    		add[rt<<1|1]+=add[rt];
    		summ[rt<<1]+=(m-(m>>1))*add[rt];
    		summ[rt<<1|1]+=(m>>1)*add[rt];
    		add[rt]=0;  //取消本层的标记 
    	 }
    }
    void build(int l,int r,int rt){  //用满二叉树建树 
    	add[rt]=0;
    	if(l==r) {
    		scanf("%lld",&summ[rt]);   //叶子节点 赋值 
    		return;
    	}
    	int mid=(l+r)/2;
    	build(l,mid-1,rt<<1);
    	build(mid+1,r,rt<<1|1);
    	pusup(rt);  //这是个递归结构    向上更新区间和 
    }
    void update(int a,int b,LL c,int l,int r,int rt){  //区间更新,
    	if(a<=l&&b>=r){   //lazy方法,包含在区间里面,就整体更新 
    		summ[rt]+=(r-l+1)*c;
    		add[rt]+=c;
    		return;
    	} 
    	push_down(rt,r-l+1);  //向下更新
    	int mid=(l+r)/2; 
    	//这里就是在处理有分叉的情况 
    	if(a<=mid) update(a,b,c,l,mid-1,rt<<1);  //分成两半,继续深入 
    	if(b>mid) update(a,b,c,mid+1,r,rt<<1|1);
    	pusup(rt);  //向上更新 
    }
    
    LL que(int a,int b,int l,int r,int rt){  //区间求和 
    	if(a<=l&&b>=r) return summ[rt];  //满足lazy直接返回
    	push_down(rt,r-l+1); //向下更新 
    	int mid=(l+r)/2;
    	LL ans=0;
    	if(a<=mid) ans+=que(a,b,l,mid-1,rt<<1);
    	if(b>mid) ans+=que(a,b,mid+1,r,rt<<1|1);
    	return ans; 
    }
    int main(){
    	int n,m;
    	scanf("%d %d",&n,&m);
    	build(1,n,1);  //先建树 
    	
    	while(m--){
    		string str;
    		int a,b;
    		LL c;
    		cin>>str;
    		if(str[0]=='C'){
    			scanf("%d %d %lld",&a,&b,&c);
    			update(a,b,c,1,n,1);
    		}
    		else{
    			scanf("%d %d",&a,&b);
    			printf("%lld
    ",que(a,b,1,n,1));
    		}
    	}
    	
    return 0;
    }
    

      

    1547:【 例 1】区间和

    点修改、区间求和 

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //模板题:点修改、区间查询 
    int n,m;
    LL summ[maxn*4];
    /*
    void build(int l,int r,int root){
    	summ[root]=0;
    	if(l==r) return;
    	int mid=(l+r)/2;
    	build(1,mid,root*2);
    	build(mid+1,r,root*2+1);
    	summ[root]=summ[root*2]+summ[root*2+1];
    }
    */
    LL que(int root,int l,int r,int x,int y){   //调用的时候: upda(1,1,n,a,b) 
    	if(r<x||y<l) return 0; //如果要求的区间与找到的区间交集为空,返回 
    	if(l>=x&&y>=r) return summ[root];//如果找到的区间包含于要求的区间,返回这个区间的值 
    	int mid=(l+r)/2;
    	return que(root*2,l,mid,x,y)+que(root*2+1,mid+1,r,x,y);
    }
    void upda(int root,int l,int r,int a,int b){  //调用的时候: upda(1,1,n,a,b) 
    	if(a<l||a>r) return;
    	if(l==r&&l==a){    //点修改 
    		summ[root]+=b;
    		return ;
    	}
    	int mid=(l+r)/2;
    	upda(root*2,l,mid,a,b);
    	upda(root*2+1,mid+1,r,a,b);
    	summ[root]=summ[root*2]+summ[root*2+1];   //在这里回溯的时候修改 
    }
    int main(){
    	scanf("%d %d",&n,&m);
    	int k,a,b;
    	///build(1,n,1); //在这里调用建树 
    	for(int i=0;i<m;i++){
    		scanf("%d %d %d",&k,&a,&b);
    		if(k==0) upda(1,1,n,a,b); //点修改,在a上加b 
    		else printf("%lld
    ",que(1,1,n,a,b));  //区间查询 
    	}
    return 0;
    }
    

      

    1548:【例 2】A Simple Problem with Integers

    区间修改(加上x),区间求和

    可以用线段树、也可以用树状数组
    感觉线段树简单一点,但是不好推
    用树状数组讲解:维护两个前缀和
    https://blog.csdn.net/gzcszzx/article/details/100539427
    维护两个前缀和,
    S1[i]=d[i],S2[i]=d[i]*i
    查询:位置Pos的前缀和就是(Pos+1)*S1中1到Pos的和 减去 S2中1到Pos的和,[L,R]=SS[R]-SS[L-1]

    修改:[L,R]
    S1:S1[L]+Tag,S1[R+1]-Tag
    S2:S2[L]+Tag*L ,S2[R+1]-Tag*(R+1)

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1e6+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //这道题是模板题:区间求和、区间修改
    //可以用线段树、也可以用树状数组
    //感觉线段树简单一点,但是不好推 
    //用树状数组讲解:维护两个前缀和
    //https://blog.csdn.net/gzcszzx/article/details/100539427
    /*
    维护两个前缀和,
    S1[i]=d[i],S2[i]=d[i]*i
    查询:位置Pos的前缀和就是(Pos+1)*S1中1到Pos的和 减去 S2中1到Pos的和,[L,R]=SS[R]-SS[L-1]
    
    修改:[L,R]  
    S1:S1[L]+Tag,S1[R+1]-Tag   
    S2:S2[L]+Tag*L ,S2[R+1]-Tag*(R+1)
    */
    LL n,m;
    LL a[maxn],d[maxn];  //a[i]为原数组  d[i]为差分数组
    LL c1[maxn],c2[maxn];  //两个前缀和
    #define lowbit(x) ((x)&(-x)) 
    void add(LL x,LL v){
    	LL p=x;
    	while(x<=n){
    		c1[x]+=v;
    		c2[x]+=p*v;
    		x+=lowbit(x);
    	}
    }
    LL getans(LL x){
    	LL ans=0,p=x;
    	while(x){
    		ans+=(p+1)*c1[x]-c2[x];
    		x-=lowbit(x);
    	}
    	return ans;
    }
    int main(){
    	scanf("%lld %lld",&n,&m);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&a[i]);
    		d[i]=a[i]-a[i-1];
    		add(i,d[i]);
    	}
    	while(m--){
    		int p;
    		scanf("%d",&p);
    		if(p==1){
    			LL l,r,c;
    			scanf("%lld %lld %lld",&l,&r,&c);
    			add(l,c);
    			add(r+1,-c);
    		}
    		if(p==2){
    			LL x,y;
    			scanf("%lld %lld",&x,&y);
    			printf("%lld
    ",getans(y)-getans(x-1));
    		}
    	}
    return 0;
    }
    

      

    1549:最大数

    修改:在序列最后添加数

    查询:最后L个数种最大数

    单点更新,区间查询

    这道题也有两种做法
    //但是有一种是单调队列,另一种是线段树
    //开始时创建一个大序列,全部设为 2147483647。每插入一个数,就将大序列中空闲部分的第一个数改为被插入的数,然后递归更新上层。复杂度O(nlog2n)。

     原文链接:https://blog.csdn.net/sinat_34943123/article/details/53861325

    单调队列的做法:

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=200001; 
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //单调队列的做法
    /*
    由于先入队的较小数,在有后入队的大数的情况下不可能为答案,所以,可以维护一个单调队列。由于单调队列中入队先后,与数的大小皆是有序的,
    故可以用二分查找找到单调队列中,在后l个数里,最靠近队首(最大)的数,即为答案。
    ps:(1)线段树常数大故此做法要快得多 (2)c++中可用函数lower_bound实现二分查找功能。
    原文链接:https://blog.csdn.net/sinat_34943123/article/details/53861325
    */ 
    int a[maxn];   //q是队列 
    int q[maxn];  //一个存下标,一个存值
    int m,p,num,t; 
    int main(){
    	scanf("%d %d",&m,&p);
    	t=0;
    	int tmp,tail=0,l=0;
    	char op;
    	int xx; 
    	for(int i=0;i<m;i++){
    		scanf(" %c %d",&op,&xx);
    		//cout<<l<<endl;
    		if(op=='A'){
    			scanf("%d",&xx);
    			int shuji=(t+xx)%p;
    			while(q[tail]<=shuji&&tail) tail--;
    			q[++tail]=shuji;
    			a[tail]=++l; 
    		}
    		if(op=='Q'){
    			int pos=lower_bound(a+1,a+1+tail,l-xx+1)-a;
    			t=q[pos];
    			printf("%d
    ",t);
    		}
    	}
    return 0;
    }
    

    线段树做法:

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=2e5+19;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //单点更新,区间查询?
    //这道题也有两种做法
    //但是有一种是单调队列,另一种是线段树
    //开始时创建一个大序列,全部设为 2147483647。每插入一个数,就将大序列中空闲部分的第一个数改为被插入的数,然后递归更新上层。复杂度O(n?log2n)。
    int m,p;
    int  a[maxn*4];
    void build(int root,int l,int r){   //初始化 
    	if(l>r) return;
    	a[root]=-INF;
    	int mid=(l+r)/2;
    	if(l<r){  //记得要加这个条件呀。。。。 
    		build(root*2,l,mid);
    		build(root*2+1,mid+1,r);
    	}
    }
    void upda(int root,int l,int r,int pos,int val){ //在pos位置上增加val值,也就是最后一个位置 
    	if(l>r) return;
    	if(l==r) a[root]=val;  //找到了根节点,更新
    	else{
    		int mid=(l+r)/2;
    		if(pos<=mid) upda(root*2,l,mid,pos,val);
    		else upda(root*2+1,mid+1,r,pos,val);
    		a[root]=max(a[root*2],a[root*2+1]);
    		//在这里!!!每个节点存储的是最大的孩子节点值 
    	} 
    }
    int que(int root,int l,int r,int x,int y){  //l,r是会变化的 
    	if(l>r||l>y||r<x) return -INF;
    	if(l>=x&&r<=y) return a[root];
    	int mid=(l+r)/2; 
    	return max(que(root*2,l,mid,x,y),que(root*2+1,mid+1,r,x,y));
    }
    int main(){
    	scanf("%d %d",&m,&p);
    	build(1,1,m);  //最多也只有m个数 
    	int num=0;//添加的数的个数
    	int t=0; //存储上一次的查找结果 
    	//一开始就初始化创建树,共m个节点,因为最多就m个节点
    	char op;
    	int xx; 
    	for(int i=0;i<m;i++){
    	//cout<<i<<endl;		
    		scanf(" %c %d",&op,&xx);
    		//cout<<op<<" "<<xx<<"jj"<<endl;
    		if(op=='A'){   //表示添加一个数在后面 
    			upda(1,1,m,++num,(xx+t)%p);
    		}
    		if(op=='Q') { //询问序列最后L个数中最大的数 
    			int tmp=que(1,1,m,num-xx+1,num);
    			//查询后面xx个数字
    			t=tmp;
    			printf("%d
    ",tmp); 
    		}
    		getchar();
    	}
    return 0;
    }
    

      

    1550:花神游历各国

    //区间修改、区间查询
    //并且变化很神奇,l--r中每个国家的喜欢度变为sqrt()

    注意要处理节点的值不断sqrt()后的变化,要特判是不是1或者0  mx[root]==1||mx[root]==0

    需要数组:mx[maxn*4],summ[maxn*4],num[maxn],分别存储左右孩子最大值、总和、这个节点的值

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=2e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //区间修改、区间查询
    //并且变化很神奇,l--r中每个国家的喜欢度变为sqrt()
     
    int n,m;
    LL summ[maxn*4],num[maxn];
    LL mx[maxn*4];
    void build(int l,int r,int root){
    	if(l==r) {
    		summ[root]=mx[root]=num[l];  //根节点赋值
    		return;
    	}
    	int mid=(l+r)/2; 
    	build(l,mid,root*2);
    	build(mid+1,r,root*2+1);
    	summ[root]=summ[root<<1]+summ[(root<<1)+1];  //两个子树的和 
    	mx[root]=max(mx[root<<1],mx[(root<<1)+1]);  //两个子树的最大值 
    }
    void upda(int root,int l,int r,int x,int y){
    	//看这里为什么需要mx数组!!! 
    	if(mx[root]==1||mx[root]==0) return; //不需要改变值了
    	if(l==r){
    		summ[root]=mx[root]=int(sqrt(summ[root]));
    		return;
    	} 
    	int mid=(l+r)/2;
    	if(x<=mid) upda(root*2,l,mid,x,y);
    	if(y>mid) upda(root*2+1,mid+1,r,x,y);
    	summ[root]=summ[root*2]+summ[root*2+1];
    	mx[root]=max(mx[root*2],mx[root*2+1]);
    }
    LL getans(int root,int l,int r,int x,int y){
    	if(x<=l&&r<=y) return summ[root];
    	int mid=(l+r)/2;
    	LL ans=0;
    	if(x<=mid) ans+=getans(root*2,l,mid,x,y);
    	if(y>mid) ans+=getans(root*2+1,mid+1,r,x,y);
    	return ans;
    }
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++){
    		scanf("%lld",&num[i]);
    	}
    	build(1,n,1);  //别忘列写这个TAT 
    	LL xx,ll,rr;
    	scanf("%d",&m);
    	while(m--){
    		scanf("%lld %lld %lld",&xx,&ll,&rr);
    		if(xx==1){
    			printf("%lld
    ",getans(1,1,n,ll,rr));
    		}
    		else{
    			upda(1,1,n,ll,rr);
    		}
    	}
    return 0;
    }
    

      

    1551:维护序列

    是区间修改,区间求和
    //但是修改有两种方式:1、全部乘一个值 2、全部加一个值
    //https://www.cnblogs.com/lher/p/6556238.html
    //https://blog.csdn.net/weixin_43323172/article/details/99689300

    经典线段树题目,同时有两个标记,一个加法标记,一个乘法标记,每个标记维护的意义为:下面的子树中,要先把每一项都乘以乘法标记,再加上加法标记。

    设序列A = {a1,a2,a3,…,an},如果每一项先乘以p1,则序列变为{p1*a1,p1*a2,p1*a3,...,p1*an},再加上p2,则序列变为{p1*a1+p2,p1*a2+p2,p1*a3+p2,...,p1*an+p2},
    再乘以p3,则序列变为{p1*p3*a1+p2*p3,p1*p3*a2+p2*p3,p1*p3*a3+p2*p3,...,p1*p3*an+p2*p3}。
    由此可见,在添加标记或者下放标记合并时,
    若新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
    若新加加法标记,则与前面的乘法标记无关,直接加在加法标记上,区间和加上区间长度*加法标记。

    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<stack>
    #include<cstdio>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    using namespace std;
    const int maxn=1e5+10;
    const int INF=0x3fffffff;
    typedef long long LL;
    typedef unsigned long long ull;
    //也是区间修改,区间求和
    //但是修改有两种方式:1、全部乘一个值  2、全部加一个值
    //https://www.cnblogs.com/lher/p/6556238.html
    //https://blog.csdn.net/weixin_43323172/article/details/99689300
    LL n,p,m; 
    LL summ[maxn*4];
    //要加上Lazy操作,不然会超时 
    LL lazy_add[maxn*4],lazy_mul[maxn*4];
    LL num[maxn];
    void add(int v,int l,int r,int root){
    	//区间整体加 
    	lazy_add[root]=(lazy_add[root]+v%p)%p;
    	summ[root]=(summ[root]+(LL)v*(r-l+1)%p)%p;
    }
    /*
    经典线段树题目,同时有两个标记,一个加法标记,一个乘法标记,每个标记维护的意义为:下面的子树中,要先把每一项都乘以乘法标记,再加上加法标记。
    
    设序列A = {a1,a2,a3,…,an},如果每一项先乘以p1,则序列变为{p1*a1,p1*a2,p1*a3,...,p1*an},再加上p2,则序列变为{p1*a1+p2,p1*a2+p2,p1*a3+p2,...,p1*an+p2},
    再乘以p3,则序列变为{p1*p3*a1+p2*p3,p1*p3*a2+p2*p3,p1*p3*a3+p2*p3,...,p1*p3*an+p2*p3}。
    由此可见,在添加标记或者下放标记合并时,
    若新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
    若新加加法标记,则与前面的乘法标记无关,直接加在加法标记上,区间和加上区间长度*加法标记。
    */
    void mul(int v,int l,int r,int root){
    	lazy_mul[root]=(lazy_mul[root]*v)%p;
    	lazy_add[root]=(lazy_add[root]*v)%p;   //新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
    	summ[root]=(summ[root]*v)%p;
    } 
    void push_down(int mm,int l,int r,int root){
    	if(lazy_mul[root]!=1){
    	//	int mid=(l+r)/2;
    		mul(lazy_mul[root],l,mm,root*2);
    		mul(lazy_mul[root],mm+1,r,root*2+1);
    		lazy_mul[root]=1;
    	}
    	if(lazy_add[root]!=0){
    	//	int mid=(l+r)/2;
    		add(lazy_add[root],l,mm,root*2);
    		add(lazy_add[root],mm+1,r,root*2+1);
    		lazy_add[root]=0;
    	}
    }
    void build(LL l,LL r,LL root){
    	summ[root]=0;
    	lazy_add[root]=0;
    	lazy_mul[root]=1; 
    	if(l==r) {
    		summ[root]=num[l];
    		return;
    	}
    	int mid=(l+r)/2;
    	build(l,mid,root*2);
    	build(mid+1,r,root*2+1);
    	summ[root]=(summ[root*2]+summ[root*2+1])%p;
    }
    void upda(int root,int l,int r,int x,int y,int flag,int c){
    	if(x<=l&&r<=y) {
    		if(flag==1) return mul(c,l,r,root);
    		if(flag==2) return add(c,l,r,root) ;
    		//return;
    	}
    	int mid=(l+r)/2;
    	push_down(mid,l,r,root);  //int mm,int l,int r,int root
    	if(x<=mid) upda(root*2,l,mid,x,y,flag,c);
    	if(y>mid) upda(root*2+1,mid+1,r,x,y,flag,c);
    	summ[root]=(summ[root*2]+summ[root*2+1])%p;
    }
    LL getans(int root,int l,int r,int x,int y){
    	if(x<=l&&r<=y) return summ[root];
    	int mid=(l+r)/2;
    	push_down(mid,l,r,root);
    	
    	LL ans=0;
    	if(x<=mid) ans=(ans+getans(root*2,l,mid,x,y))%p;
    	if(y>mid) ans=(ans+getans(root*2+1,mid+1,r,x,y))%p;
    	return ans%p;
    }
    int main(){
    	scanf("%lld %lld",&n,&p);
    	for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
    	build(1,n,1);
    	int op,g,c,t;
    	scanf("%d",&m);
    	while(m--){
    		scanf("%d",&op);
    		if(op==1||op==2){
    			scanf("%d %d %d",&t,&g,&c);
    			 upda(1,1,n,t,g,op,c);
    		}
    		else if(op==3){
    			scanf("%d %d",&t,&g);
    			printf("%lld
    ",getans(1,1,n,t,g));
    		}
    	}
    return 0;
    }
    

      

  • 相关阅读:
    Oracle 删除重复数据的几种方法
    12.25模拟赛T3
    java实现第五届蓝桥杯圆周率
    java实现第五届蓝桥杯武功秘籍
    Oracle 审计初步使用
    [CERC2017]Intrinsic Interval——扫描线+转化思想+线段树
    ORA-12012 Error on auto execute of job "SYS"."ORA$AT_OS_OPT_SY_<NN> in 12.2.0 Database
    12.25模拟赛T2
    java实现第五届蓝桥杯写日志
    java实现第五届蓝桥杯李白打酒
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12772698.html
Copyright © 2011-2022 走看看