线段树-进阶
上一次我們寫的線段樹已經(jīng)可以解決區(qū)間查詢、單點修改了!可喜可賀
那如果現(xiàn)在我們需要區(qū)間修改、區(qū)間查詢呢?
一般有兩種思路:lazytag和標記永久化,lazytag的使用面好像更廣一些。
一、lazytag
比如我們現(xiàn)在要修改一個區(qū)間,我們可以像查詢一樣分成若干段,然后分別修改每一段。
那么問題來了,每一段要怎么修改?如果直接修改sum,詢問就亂套了。如果暴力修改,最壞復雜度O(n),那還要線段樹干嘛(╯‵□′)╯掀桌
那我們這么想,我們修改就不修改整個區(qū)間了,而是在區(qū)間上維護一個標記。
那我們查詢的時候要怎么辦呢?我們一路走一路下傳標記,就是說把這個點的標記清掉,更新本節(jié)點的信息,并把標記傳給兒子。
那這樣又有一個問題,如果我們修改了一個點的孩子,然后查詢的時候只查詢到上面的這個點,那信息不就更新不到了嗎?
那我們查詢的時候還要用兒子節(jié)點的信息順便更新一下本節(jié)點的信息。
說了這么多…寫起來好像還是蠻簡單的。
下面這份代碼的碼風比較奇怪…就是只建了滿二叉樹,沒有按n來建…代碼里的ls和rs表示區(qū)間的左右端點…
//codevs1082 區(qū)間加一個數(shù) 區(qū)間查詢和 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <set> #include <map> using namespace std; int n; typedef long long ll; #define SZ 555555 int M=262144,M2=M+M,ls[SZ],rs[SZ]; ll sum[SZ],tag[SZ]; void build() {for(int i=M+1;i<=M+M;i++) ls[i]=rs[i]=i-M;for(int i=M-1;i;i--) ls[i]=ls[i+i], rs[i]=rs[i+i+1], sum[i]=sum[i+i]+sum[i+i+1]; } void pd(int x) {if(tag[x]){sum[x]+=tag[x]*(rs[x]-ls[x]+1);if(x+x<=M2) tag[x+x]+=tag[x], tag[x+x+1]+=tag[x];tag[x]=0;} } void upd(int x) {pd(x+x); pd(x+x+1);sum[x]=sum[x+x]+sum[x+x+1]; } void edit(int x,int ql,int qr,int v) {if(x>M2||ql>qr) return;pd(x);if(ql==ls[x]&&qr==rs[x]) {tag[x]+=v; return;}int mid=ls[x]+rs[x]>>1;edit(x+x,ql,min(qr,mid),v);edit(x+x+1,max(mid+1,ql),qr,v);upd(x); } ll gsum(int x,int ql,int qr) {if(x>M2||ql>qr) return 0;pd(x);if(ql==ls[x]&&qr==rs[x]) return sum[x];int mid=ls[x]+rs[x]>>1;ll ans=gsum(x+x,ql,min(qr,mid))+gsum(x+x+1,max(mid+1,ql),qr);upd(x); return ans; } int q,a,b,c; char buf[3]; void readdata() {scanf("%d",&n);for(int i=1;i<=n;i++) scanf("%lld",&sum[i+M]);scanf("%d",&q); build();while(q--){scanf("%s",buf);if(buf[0]=='2'){scanf("%d%d",&a,&b);printf("%lld\n",gsum(1,a,b));}else{scanf("%d%d%d",&a,&b,&c);edit(1,a,b,c);}} } int main() {readdata(); }
二、標記永久化
你可能會覺得:lazytag使用起來很方便,感覺也很強大,為啥還要什么標記永久化?
等你學了主席樹你就明白了 反正多學總沒有什么問題233
標記永久化就是如果我們要修改一個區(qū)間,還是在區(qū)間上打一個標記,但是不下傳!
——啥,不下傳如何保證正確性?
我們在修改的路徑上更新這個區(qū)間的和,然后在返回和的時候把標記累加進去,這樣就可以保證正確性了。
//codevs1082 區(qū)間加一個數(shù) 區(qū)間查詢和 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <math.h> #include <set> #include <map> using namespace std; int n; typedef long long ll; #define SZ 555555 int MAXN=524288; ll sum[SZ],tag[SZ]; void edit(int x,int ql,int qr,int v,int l,int r) {if(x>MAXN||ql>qr||l>r) return;if(ql==l&&qr==r) {tag[x]+=v; return;}sum[x]+=(qr-ql+1)*v;int mid=l+r>>1;edit(x+x,ql,min(qr,mid),v,l,mid);edit(x+x+1,max(mid+1,ql),qr,v,mid+1,r); } ll gsum(int x,int ql,int qr,int l,int r) {if(x>MAXN||ql>qr) return 0;if(ql==l&&qr==r) return sum[x]+tag[x]*(qr-ql+1);int mid=l+r>>1;return gsum(x+x,ql,min(qr,mid),l,mid)+gsum(x+x+1,max(mid+1,ql),qr,mid+1,r)+tag[x]*(qr-ql+1); } int q,a,b,c; char buf[3]; void readdata() {scanf("%d",&n);for(int i=1;i<=n;i++){int a=i,b; scanf("%d",&b);edit(1,a,a,b,1,n);}scanf("%d",&q);while(q--){scanf("%s",buf);if(buf[0]=='2'){scanf("%d%d",&a,&b);printf("%lld\n",gsum(1,a,b,1,n));}else{scanf("%d%d%d",&a,&b,&c);edit(1,a,b,c,1,n);}} } int main() {readdata(); }
我相信你學完這兩種方法,線段樹的題都可以隨手秒啦!
轉載于:https://www.cnblogs.com/zzqsblog/p/5244070.html
總結
- 上一篇: 知识点回顾-简单的TableView单组
- 下一篇: [重磅] 让HTML5达到原生的体验 系