RMQ问题:区间最大值或者最小值问题,类似的还要区间和问题
操作:
(1)求最值 、求和:区间内
(2)修改元素 :点修改、区间修改
线段树:用于区间处理的数据结构,用二叉树构造
二叉折半查找,查找点或者区间的时候:顺着往下查找 。存储空间:4n
修改点:直接修改叶子节点,然后自底向上更新
修改区间:使用lazy标记,加上pushdown函数,更新区间的lazy标记
复杂度:O(nlogn),线段是把n个数按照二叉树进行分组,每次更新有关节点的时候,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数
last cows
第一种做法:用结构体实现线段树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #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\n" ,ans[i]); } return 0; } |
第二种做法:完全二叉树(数组)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #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\n" ,ans[i]); return 0; } |
当数据太大:也可以考虑离散化,把原有的大二叉树压缩为小二叉树,但是压缩前后子区间的关系不变
区间修改
操作:(1)加 (2)查询和
lazy_tag方法:当修改一个整块区间时,只对这个线段区间进行整体上的修改,其内部每个元素内容先不修改,只有当这部分线段的一致性被破坏时才把变化之传给子区间(查询时也一样)
tag[]数组:记录节点i是否用到lazy原理,其值是op a b c中的c,如果做了多次lazy,那么add[]可以累加,如果在某次操作中被深入, 破坏了lazy,那么add[]归0
1548:【例 2】A Simple Problem with Integers(线段树的做法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | #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; int add[maxn*4],a[maxn]; long long summ[maxn*4]; int n,m; inline int getin(){ //读入优化 char c; int sgn=1; while ((c= getchar ())< '0' ||c> '9' ) if (c== '-' ) sgn=-1; int res=c- '0' ; while ((c= getchar ())>= '0' &&c<= '9' ) res=res*10+c- '0' ; return res*=sgn; } void build( int l, int r, int rt){ if (l==r){ summ[rt]=a[l]; return ; } int mid=l+r>>1; build(l,mid,rt<<1); build(mid+1,r,rt<<1|1); summ[rt]=summ[rt<<1]+summ[rt<<1|1]; //位运算优化常数 } void adde( int rt, int l, int r, int v){ add[rt]+=v; summ[rt]+=( long long )v*(r-l+1); } void pushdown( int rt, int l, int r, int mid){ //标记下方 if (add[rt]==0) return ; adde(rt<<1,l,mid,add[rt]); adde(rt<<1|1,mid+1,r,add[rt]); add[rt]=0; } long long que( int rt, int l, int r, int x, int y){ if (l>=x&&r<=y) return summ[rt]; int mid=l+r>>1; long long res=0; pushdown(rt,l,r,mid); if (x<=mid) res+=que(rt<<1,l,mid,x,y); if (mid<y) res+=que(rt<<1|1,mid+1,r,x,y); return res; } void chan( int rt, int l, int r, int x, int y, int v){ if (l>=x&&r<=y) { return adde(rt,l,r,v); } int mid=l+r>>1; pushdown(rt,l,r,mid); if (x<=mid) chan(rt<<1,l,mid,x,y,v); if (mid<y) chan(rt<<1|1,mid+1,r,x,y,v); summ[rt]=summ[rt<<1]+summ[rt<<1|1]; } int main(){ scanf ( "%d %d" ,&n,&m); for ( int i=1;i<=n;i++) scanf ( "%d" ,&a[i]); build(1,n,1); while (m--){ int d,l,r,x; scanf ( "%d " ,&d); if (d==1){ scanf ( "%d %d %d" ,&l,&r,&x); chan(1,1,n,l,r,x); } else { scanf ( "%d %d" ,&l,&r); printf ( "%lld\n" ,que(1,1,n,l,r)); } } return 0; } |
1547:【 例 1】区间和
点修改、区间求和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #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\n" ,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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | #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\n" ,getans(y)-getans(x-1)); } } return 0; } |
1549:最大数
修改:在序列最后添加数
查询:最后L个数种最大数
单点更新,区间查询
这道题也有两种做法
//但是有一种是单调队列,另一种是线段树
//开始时创建一个大序列,全部设为 2147483647。每插入一个数,就将大序列中空闲部分的第一个数改为被插入的数,然后递归更新上层。复杂度O(nlog2n)。
原文链接:https://blog.csdn.net/sinat_34943123/article/details/53861325
单调队列的做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #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\n" ,t); } } return 0; } |
线段树做法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #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\n" ,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],分别存储左右孩子最大值、总和、这个节点的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | #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\n" ,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}。
由此可见,在添加标记或者下放标记合并时,
若新加乘法标记,则原有的乘法标记,加法标记和区间和都乘以新加的乘法标记,
若新加加法标记,则与前面的乘法标记无关,直接加在加法标记上,区间和加上区间长度*加法标记。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | #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\n" ,getans(1,1,n,t,g)); } } return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步