众所周知,当涉及到线段树的区间修改时,往往会引入lazy标记,通过懒惰标记的push_down优化区间修改和查询
如这道板题: 题目链接
1 #include <bits/stdc++.h> 2 #define Lson(x) ((x)<<1) 3 #define Rson(x) ((x)<<1|1) 4 using namespace std; 5 6 const int maxn =1e5; 7 int d[(maxn<<2)+10]; 8 int lazy[(maxn<<2)+10]; 9 int raw[maxn+10]; 10 void build(int s,int t,int p){ 11 if(s==t){ 12 d[p] = raw[s]; 13 return; 14 } 15 int mid = (s+t)>>1; 16 build(s,mid,Lson(p)); 17 build(mid+1,t,Rson(p)); 18 d[p] = d[Lson(p)] + d[Rson(p)]; 19 return; 20 } 21 22 void update(int L,int R,int C,int s,int t,int p){ 23 if(L==s&&t==R){ 24 d[p] = (R-L+1)*C; 25 lazy[p] = C; 26 return ; 27 } 28 29 int mid = (s+t)>>1; 30 31 if(lazy[p]&&s!=t){//not leaf node and lazy 32 d[Lson(p)] = (mid - s + 1 )*lazy[p];lazy[Lson(p)] = lazy[p]; 33 d[Rson(p)] = (t - mid)*lazy[p];lazy[Rson(p)] = lazy[p]; 34 lazy[p] = 0; 35 } 36 37 if(R<=mid) update(L,R,C,s,mid,Lson(p)); 38 else if(L>mid) update(L,R,C,mid+1,t,Rson(p)); 39 else update(L,mid,C,s,mid,Lson(p)) , update(mid+1,R,C,mid+1,t,Rson(p)); 40 d[p] = d[Lson(p)] + d[Rson(p)]; 41 } 42 43 int query(int L,int R,int s,int t,int p){ 44 if(L==s&&t==R){ 45 return d[p]; 46 } 47 48 int mid = (s+t)>>1; 49 if(lazy[p]&&s!=t){ 50 d[Lson(p)] = (mid - s + 1 )*lazy[p];lazy[Lson(p)] = lazy[p]; 51 d[Rson(p)] = (t - mid)*lazy[p];lazy[Rson(p)] = lazy[p]; 52 lazy[p] = 0; 53 } 54 55 int sum = 0; 56 if(R<=mid) sum+=query(L,R,s,mid,Lson(p)); 57 else if(L>mid) sum+=query(L,R,mid+1,t,Rson(p)); 58 else sum = query(L,mid,s,mid,Lson(p)) + query(mid+1,R,mid+1,t,Rson(p)); 59 return sum; 60 } 61 62 int main(){ 63 int N; 64 scanf("%d",&N); 65 for(int i=1;i<=N;++i){ 66 scanf("%d",&raw[i]); 67 } 68 build(1,N,1); 69 int Q; 70 scanf("%d",&Q); 71 for (int i = 0; i < Q; ++i) { 72 int op; 73 scanf("%d",&op); 74 if(op) { 75 int L,R,C; 76 scanf("%d%d%d",&L,&R,&C); 77 update(L,R,C,1,N,1); 78 } else { 79 int L,R; 80 scanf("%d%d",&L,&R); 81 printf("%d ",query(L,R,1,N,1)); 82 } 83 } 84 return 0; 85 }
而关于lazy标记,还有一种标签永久化的思路,还是一道板题: 题目链接
这道题当然可以用正常的下推lazy标记写,但如果我们想标记永久化,减少下推过程也是可以做的。
update部分搜到一点只改标签,然后改一路以来的 d[] 结点值(这点很重要,不然查询时,查到一个中间点可能结束,曾经对其后代的修改就丢失了)。query部分搜索时同时用ever记录一路以来的lazy标记值之和,ever其实就是应该增加却没有用过的值,搜到中间返回时计算区间和(d[]结点值,lazy标记与ever)。此题 lazy标记怎么加也不会爆int,但区间和可能会爆int。
1 //#include <bits/stdc++.h> 2 #include <cstdio> 3 using namespace std; 4 #define Lson(x) ((x)<<1) 5 #define Rson(x) ((x)<<1|1) 6 typedef long long ll; 7 const int maxn = 1e5; 8 ll d[(maxn<<2)+10]; 9 int lazy[(maxn<<2)+10]; 10 int raw[maxn+10]; 11 12 void build(int s,int t,int p){ 13 if(s==t){ 14 d[p] = 1ll*raw[s]; 15 lazy[p] = 0; 16 return ; 17 } 18 int mid = (s+t) >> 1; 19 build(s,mid,Lson(p)); 20 build(mid+1,t,Rson(p)); 21 lazy[p] = 0; 22 d[p] = d[Lson(p)] + d[Rson(p)]; 23 } 24 25 void update(int L,int R, int C, int s,int t,int p){ 26 if(L==s && t==R){ 27 lazy[p] += 1ll*C; 28 return ; 29 } 30 31 d[p] += 1ll*(R-L+1)*C;//这一句保证当前点接受对其后代的修改 32 33 int mid = (s+t) >> 1; 34 if(R<=mid) 35 update(L,R,C,s,mid,Lson(p)); 36 else if(L>mid) 37 update(L,R,C,mid+1,t,Rson(p)); 38 else update(L,mid,C,s,mid,Lson(p)) , update(mid+1,R,C,mid+1,t,Rson(p)); 39 } 40 41 ll query(int L,int R,int s,int t,int p,int ever){ 42 if(L==s && t==R){ 43 return 1ll*d[p] + 1ll*(t-s+1) * (lazy[p] + ever); 44 } 45 int mid = (s+t)>>1; 46 ll sum = 0; 47 if(R<=mid) sum += query(L,R,s,mid,Lson(p),ever + lazy[p]); 48 else if(L>mid) sum += query(L,R,mid+1,t,Rson(p),ever + lazy[p]); 49 else sum = query(L,mid,s,mid,Lson(p),ever + lazy[p]) + query(mid+1,R,mid+1,t,Rson(p),ever + lazy[p]); 50 return sum; 51 } 52 53 int main(){ 54 int N,Q; 55 while(~scanf("%d%d",&N,&Q)){ 56 for (int i = 1; i <= N; ++i) { 57 scanf("%d",&raw[i]); 58 } 59 build(1,N,1); 60 for (int i = 0; i < Q; ++i) { 61 char op[3]; 62 scanf("%s",op); 63 if(op[0] == 'Q'){ 64 int a, b; 65 scanf("%d%d",&a,&b); 66 printf("%lld ",query(a,b,1,N,1,0)); 67 } 68 else{ 69 int a,b,c; 70 scanf("%d%d%d",&a,&b,&c); 71 update(a,b,c,1,N,1); 72 } 73 } 74 } 75 return 0; 76 }
d[p] += 1ll*(R-L+1)*C;//这一句保证当前点接受对其后代的修改
开始少了这句相当于丢掉了许多修改,因为query是会搜到一半就结束的,没有pushdown和maitain就需要修改一路以来的值。
关于标签永久化,再请看这题: 题目链接
题意是一个长为 n 的序列 a[n](下标1~n) ,初始时每个元素为0,现在有m次操作L , R ,V将区间[L, R]里小于V的元素更新为V,最后询问每个 ai * i 的异或和。这道题的输入是根据三个初始数字和一个函数不断生成的,可以看做是随机的区间修改。(n<=1e5,m<=5e6)
“区间[L, R]里小于V的元素更新为V”那肯定是有的元素更新有的不更新,而最后的询问是每个元素的值都用上了,值得注意的是只有一个询问。
于是我们可以采用lazy标记永久化的思路,每一次区间修改中没有标记的push_down也没有maintain,只是单纯地更新区间lazy标记(如此线段树只需要存lazy标记),而在最后的查询里DFS搜到底部的过程中,我们只需要记录一路走到该点的最大标记值,然后计算对答案贡献即可。之前打标记复杂度是O(mlogn) 最后DFS遍历所有点O(nlogn)。
1 #include <bits/stdc++.h> 2 #define Lson(x) ((x)<<1) 3 #define Rson(x) ((x)<<1|1) 4 using namespace std; 5 6 const int maxn = 1e5; 7 const unsigned int mod = 1<<30; 8 //const pair<int ,int > KONG = make_pair(0,0); 9 unsigned X,Y,Z; 10 //int d[(maxn<<2)+10]; 11 int lazy[(maxn<<2)+10]; 12 long long ans; 13 unsigned int RNG61(){ 14 X = X^(X<<11); 15 X = X^(X>>4); 16 X = X^(X<<5); 17 X = X^(X>>14); 18 unsigned W = X ^(Y ^ Z); 19 X = Y; 20 Y = Z; 21 Z = W; 22 return Z; 23 } 24 25 void build(int s,int t,int p){ 26 if(s==t){ 27 // d[p] = 0; 28 lazy[p] = 0; 29 return ; 30 } 31 int mid = (s+t)>>1; 32 build(s,mid,Lson(p)); 33 build(mid+1,t,Rson(p)); 34 // d[p] = 0; 35 lazy[p] = 0; 36 return; 37 } 38 39 void update(int L,int R,int C,int s,int t,int p){ 40 if(C < lazy[p]) return;//根本不用往下搜,剪枝 41 42 if(L==s && R==t){ 43 lazy[p] = max(lazy[p],C); 44 return ; 45 } 46 int mid = (s+t) >> 1; 47 if(R<=mid) update(L,R,C,s,mid,Lson(p)); 48 else if(L>mid) update(L,R,C,mid+1,t,Rson(p)); 49 else update(L,mid,C,s,mid,Lson(p)),update(mid+1,R,C,mid+1,t,Rson(p)); 50 } 51 52 53 void DFS(int s,int t,int p,int tmax){ 54 tmax = max(lazy[p],tmax); 55 if(s==t){ 56 // printf("%d :%d ",s,tmax); 57 ans ^= 1ll*s*tmax; 58 return; 59 } 60 int mid = (s+t)>>1; 61 DFS(s,mid,Lson(p),tmax); 62 DFS(mid+1,t,Rson(p),tmax); 63 return ; 64 } 65 66 int main(){ 67 // __clock_t stt = clock(); 68 int T; 69 cin >> T; 70 while(T--){ 71 int N,M; 72 cin >> N >> M >> X >> Y >> Z; 73 unsigned int n = static_cast<unsigned int>(N); 74 build(1,N,1); 75 for(int i=1;i<=M;++i){ 76 unsigned int f1 = RNG61(); 77 unsigned int f2 = RNG61(); 78 unsigned int f3 = RNG61(); 79 int L = (f1 % n) + 1; 80 int R = (f2 % n) + 1; 81 int V = f3 % (1<<30); 82 update(min(L,R),max(L,R),V,1,N,1); 83 } 84 ans = 0; 85 DFS(1,N,1,0); 86 printf("%lld ",ans); 87 } 88 // __clock_t ent = clock(); 89 // printf("Used Time: %.3lf", static_cast<double>(ent - stt)/1000.0); 90 return 0; 91 }
开始写的还是TLE了,自己测了测时间大概20秒,DFS遍历所有点是必须的,那么其实可以在打标记时进行剪枝优化:
if(C < lazy[p]) return;//根本不用往下搜,剪枝
当前区间的lazy标记值已经大于修改值C,那么这个lazy标记将会覆盖C的影响,所以没有必要往下方更新。
最后2秒通过,随机数据还是能剪枝不少。