洛谷P3373 【模板】线段树 2 && P2023 [AHOI2009]维护序列——题解
题目传送: P3373 【模板】线段树 2 P2023 [AHOI2009]维护序列
该题较传统线段树模板相比多了一个区间乘的操作。一提到线段树的区间维护问题,就自然想到了“懒标记”:为了降低时间复杂度,我们只需将要要查询的区间的真实值更新出来,而不至于一直细分到区间的每个单元,并给更新真实值的区间附加一个“懒标记”,表示他的后代们还没有被更新。但是题中既有区间加,又有区间乘,一个懒标记难以轻松地同时把加和乘表示,怎么办呢?用两个懒标记不就好了嘛。设lzsum[i]、lzmul[i]分别为i节点表示加的懒标记和表示乘的懒标记。
接下来如何下传懒标记呢?这就看我们要怎么用懒标记由标记前的状态表示标记后的状态了。因为只有加和乘,我们考虑先加还是先乘。
先加的话,用懒标记更新状态的方程即为tree[son]=(tree[son]+lzsum[father])*lzmul[father]。这个式子一看就让人摸不到维护lzsum和lzmul的头绪,很难适用于编程实现,看看另一种情况吧;
先乘的话,用懒标记更新状态的方程即为tree[son]=tree[son]*lzmul[father]+lzsum[father]*区间长度。分析一下这个式子,设想当再乘一个数k时,tree[son]直接*=k就OK了,这样不就相当于原式中的lzmul[father]乘上个kk且lzsum[father]也乘上个k吗(乘法分配律)?如果再加一个数k的话,只需让式子中的lzsum[father]+k就行了。这样我们就找到了一个下传的策略:先乘再加。同时我们还分析出了更新lzsum和lzmul的方法。
看到这里,不难发现懒标记的实际意义就是目前区间的每个元素较他自己有懒标记前的值在一顿区间加区间乘操作后等效于要乘几倍后再加几。由此知,当懒标记由父节点下传、去更新儿子的真实值时,父亲懒标记代表的“要乘几倍后再加几”,就是儿子“要乘几倍后再加几”。
题中提到数据太大、输出取模后的结果,那就在算式里多模几下就行了。
上AC代码!:
1 #include<iostream> 2 #include<cstdio> 3 #include<cctype> 4 #include<cstring> 5 #include<stack> 6 #include<cmath> 7 using namespace std; 8 long long tree[262144],mod,a[100001],lzsum[262144],lzmul[262144]; 9 long long ans; 10 char ch; 11 long long read() 12 { 13 ans=0; 14 ch=getchar(); 15 while(!isdigit(ch)) ch=getchar(); 16 while(isdigit(ch)) ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); 17 return ans; 18 } 19 void build(int t,int l,int r)//线段树的初始化 20 { 21 if(l==r) tree[t]=a[l]; 22 else 23 { 24 int mid=(l+r)/2,ls=t*2,rs=ls+1; 25 build(ls,l,mid); 26 build(rs,mid+1,r); 27 tree[t]=(tree[ls]+tree[rs])%mod; 28 } 29 lzmul[t]=1; 30 } 31 stack<char>pri; 32 void print(long long a)//输出优化,显效甚微,嫌麻烦用scanf就行。 33 { 34 if(!a)//写这样的输出优化别忘了在判断a=0的时候。(因为下文默认a>0。如果开头不特判的话, 35 //当a=0时程序会直接跳过两个while,只会输出一个回车) 36 { 37 putchar('0'); 38 putchar('\n'); 39 return; 40 } 41 while(a>0) 42 { 43 pri.push(a%10+'0'); 44 a/=10; 45 } 46 while(!pri.empty()) 47 { 48 putchar(pri.top()); 49 pri.pop(); 50 } 51 putchar('\n'); 52 } 53 void down(int t,int l,int r)//懒标记的下传 54 { 55 if(lzsum[t]==0&&lzmul[t]==1) return; 56 int ls=t*2,rs=ls+1; 57 tree[ls]=(tree[ls]*lzmul[t]+lzsum[t]*((r-l+2)/2))%mod; 58 tree[rs]=(tree[rs]*lzmul[t]+lzsum[t]*((r-l+1)/2))%mod; 59 lzmul[ls]=(lzmul[ls]*lzmul[t])%mod; 60 lzmul[rs]=(lzmul[rs]*lzmul[t])%mod; 61 lzsum[ls]=(lzsum[ls]*lzmul[t]+lzsum[t])%mod; 62 lzsum[rs]=(lzsum[rs]*lzmul[t]+lzsum[t])%mod; 63 lzsum[t]=0; 64 lzmul[t]=1;//注意lzmul的初始状态应该为一,因为一个数乘1才等于它本身 65 } 66 void mul(int t,int l,int r,int ll,int rr,int k) 67 { 68 if(ll<=l&&r<=rr) 69 { 70 tree[t]=(tree[t]*k)%mod; 71 lzmul[t]=(lzmul[t]*k)%mod; 72 lzsum[t]=(lzsum[t]*k)%mod; 73 return; 74 } 75 down(t,l,r); 76 int mid=(l+r)/2,ls=t*2,rs=ls+1; 77 if(ll<=mid) mul(ls,l,mid,ll,rr,k); 78 if(rr>mid) mul(rs,mid+1,r,ll,rr,k); 79 tree[t]=(tree[ls]+tree[rs])%mod;//儿子更新可不能忘了爹啊 80 } 81 void sum(int t,int l,int r,int ll,int rr,int k) 82 { 83 if(ll<=l&&r<=rr) 84 { 85 tree[t]=(tree[t]+k*(r-l+1))%mod; 86 lzsum[t]=(lzsum[t]+k)%mod; 87 return; 88 } 89 down(t,l,r); 90 int ls=t*2,rs=ls+1,mid=(l+r)/2; 91 if(ll<=mid) sum(ls,l,mid,ll,rr,k); 92 if(rr>mid) sum(rs,mid+1,r,ll,rr,k); 93 tree[t]=(tree[ls]+tree[rs])%mod; 94 } 95 void fin(int t,int l,int r,int ll,int rr) 96 { 97 if(ll<=l&&r<=rr) 98 { 99 ans=(ans+tree[t])%mod; 100 return; 101 } 102 down(t,l,r); 103 int ls=t*2,rs=ls+1,mid=(l+r)/2; 104 if(ll<=mid) fin(ls,l,mid,ll,rr); 105 if(rr>mid) fin(rs,mid+1,r,ll,rr); 106 } 107 int main() 108 { 109 int n=read(),m; 110 mod=read(); 111 for(int i=1;i<=n;i++) 112 a[i]=read(); 113 build(1,1,n); 114 m=read(); 115 int order,l,r,k; 116 for(int i=1;i<=m;i++) 117 { 118 order=read(); 119 if(order==1) 120 { 121 l=read(),r=read(),k=read(); 122 mul(1,1,n,l,r,k); 123 } 124 if(order==2) 125 { 126 l=read(),r=read(),k=read(); 127 sum(1,1,n,l,r,k); 128 } 129 if(order==3) 130 { 131 l=read(),r=read(); 132 ans=0; 133 fin(1,1,n,l,r); 134 print(ans); 135 } 136 } 137 return 0; 138 }