zkw segment-tree 真是太棒了(真的重口味)!寫篇博客記念入門node
emmm...首先咱們來介紹一下 zkw 線段樹這個東西(俗稱 "重口味" ,與 KMP 相似,咳咳...)ios
其實 zkw 線段樹和普通線段樹區別沒多大(區別可大了去了!)git
emmm...起碼它們的思想是一致的,都是節點維護區間信息嘛。數組
只不過...普通線段樹的維護和查詢是遞歸式,而 zkw線段樹是循環式的...ide
可是不要覺得 zkw線段樹只是靠循環加速上位的!ui
zkw線段樹能支持很是多強(luan)如(qi)閃(ba)電(zao)的操做(最後例題講)。lua
emmm...這裏你看着 普通線段樹 的節點比 zkw線段樹 的小對吧,但其實二者差很少,(由於線段樹是要開4倍空間的啊,這裏只是沒有畫出用不到的節點罷了),spa
其實上圖...仍是沒法體現zkw 線段樹的具體形態的,(可是相信聰明的你必定看懂了因此我就不講了).net
emmm...因而乎仍是上圖解釋一切3d
首先你要寫個循環,讓 m 這個值(也就是非葉子節點)大於 n (也就是總葉子結點數),以此保證 這棵樹的葉子 可以容納你要維護的 n 個值
而後你要從 m 倒推 到 1 號節點(注意是 m 倒推回 1 ,保證維護每一個節點時該節點的孩子都已經被維護完畢),讓每一個節點維護它左右孩子的信息。
這裏咱們假設要維護的信息有:區間和,區間最小值,區間最大值 。 下同
inline void build(int n){ // 維護這麼多信息都只須要這麼幾行,可見維護信息單一時代碼應該會短的不像話(壓行過的話大概三四行) for(m=1;m<=n;m<<=1); for(int i=m+1;i<=m+n;++i) sum[i]=mn[i]=mx[i]=read(); for(int i=m-1;i;--i) sum[i]=a[i<<1]+a[i<<1|1], mn[i]=min(mn[i<<1],mn[i<<1|1]), mx[i]=max(mx[i<<1],mx[i<<1|1]); }
可是,這裏對 mn 和 mx 的處理是在無修改操做的基礎上實行的,因此這樣寫並不支持修改操做。
那麼咱們能夠這樣寫:
inline void build(){ for(m=1;m<=n;m<<=1); for(int i=m+1;i<=m+n;++i) sum[i]=mn[i]=mx[i]=read(); for(int i=m-1;i;--i){ sum[i]=sum[i<<1]+sum[i<<1|1]; mn[i]=min(mn[i<<1],mn[i<<1|1]), mn[i<<1]-=mn[i],mn[i<<1|1]-=mn[i]; mx[i]=max(mx[i<<1],mx[i<<1|1]), mx[i<<1]-=mx[i],mx[i<<1|1]-=mx[i]; } }
PS:如下的操做(單點、區間更新,單點、區間查詢)所附的代碼,都基於可修改的版本
這個單點更新仍是比較好解決的,你只要找到更新的節點所在的葉子結點,而後修改後一直向父節點更新便可。
(這個。。。就不用上圖了吧...你腦補一下就差很少了)
這裏咱們假設將一個節點的值增長 v (修改的話...就記錄一下原數組,而後算差值就行了吧?)
inline void update_node(int x,int v,int A=0){ x+=m,mx[x]+=v,mn[x]+=v;for(;x>1;x>>=1){ sum[x]+=v; A=min(mn[x],mn[x^1]); mn[x]-=A,mn[x^1]-=A,mn[x>>1]+=A; A=max(mx[x],mx[x^1]), mx[x]-=A,mx[x^1]-=A,mx[x>>1]+=A; } }
這個東西...有點麻煩(你得稍微感性理解)。
就是說...你每次要更新一段區間的時候,你要讓左端點 -1 ,右端點 +1 。
而後你在更新權值的時候要判斷 左端點當前所處的節點是不是它父節點的左孩子,
是的話就讓該節點的兄弟(也就是它父節點的右孩子)獲得更新,不然不作處理,
而後左節點再向右移一位(也就是跳到了父節點),重複迭代以上步驟。
那麼右端點呢?其實也就是和左端點反着來了而已。
還有一點,循環的終止條件?這個簡單,就是當左右端點所處的節點是兄弟節點的時候結束循環。
相似的,你更新一個節點時 一樣能夠用這種方法維護(只不過這樣就更麻煩了啊)。
這樣咱們能夠看到要被更新的區間都已經被染成黃色了。可是,zkw 沒有下傳標記啊!
那麼咱們查詢的區間若是在染成黃色的節點的下部(也就是黃色節點的子樹內)該怎麼辦?
咱們能夠這樣...這樣...沒錯!標記永久化!
由於咱們已經將一個節點的標記永久化了,那麼在該節點被訪問到的時候,只要將當前查詢到的、包含在該節點所管轄區間範圍內的 區間長度乘上標記值,累加入答案便可。
(具體實現得看代碼)
同窗們有沒有注意到一種區間查詢的特殊狀況?沒錯,就是右區間+1後到達下一層的特殊狀況
就以上圖爲例,假設維護區間爲 1 ~ 7 ,如今對 2 ~ 7 進行區間加操做,那麼 t = 7+1 = 8 ,因而 t 就到達了不存在的第 5 層!
如今你想的必定是這種狀況該怎麼避免這種狀況(其實很簡單,你在建樹肯定 m 的值的時候,將判斷條件改爲 " m<=n+1 " 就好了)
但我如今要證實這種狀況不須要避免也不會出問題(基本上...吧?)
咱們能夠看到,s 和 t 在跳到 0 和 1 時知足了終止條件,而且須要更新的節點都獲得了更新,並且,其實 t 就沒有更新過節點...
這裏咱們假設要將一段區間的每一個數加上 v ,而後維護的信息同上
inline void update_part(int s,int t,int v){ int A=0,lc=0,rc=0,len=1; for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ //在這裏的 add 就是標記數組了 if(s&1^1) add[s^1]+=v,lc+=len, mn[s^1]+=v,mx[s^1]+=v; if(t&1) add[t^1]+=v,rc+=len, mn[t^1]+=v,mx[t^1]+=v; sum[s>>1]+=v*lc, sum[t>>1]+=v*rc; A=min(mn[s],mn[s^1]),mn[s]-=A,mn[s^1]-=A,mn[s>>1]+=A, A=min(mn[t],mn[t^1]),mn[t]-=A,mn[t^1]-=A,mn[t>>1]+=A; A=max(mx[s],mx[s^1]),mx[s]-=A,mx[s^1]-=A,mx[s>>1]+=A, A=max(mx[t],mx[t^1]),mx[t]-=A,mx[t^1]-=A,mx[t>>1]+=A; } for(lc+=rc;s>1;s>>=1){ sum[s>>1]+=v*lc; A=min(mn[s],mn[s^1]),mn[s]-=A,mn[s^1]-=A,mn[s>>1]+=A, A=max(mx[s],mx[s^1]),mx[s]-=A,mx[s^1]-=A,mx[s>>1]+=A; } }
這裏的 lc 和 rc 的所表明的含義須要講一下
lc 表明左端點所處的節點下有多少長度的區間在更新區間內, rc 同理 ,通俗一點地說,就是 s 和 t 所分別走過的節點中包含的更新過的區間的總長
這個沒什麼好說的吧,你從葉子結點一直跳父節點,把途中節點的 mn (或者 mx )權值累加,最後獲得的就是答案
inline int query_node(int x,int ans=0){ for(x+=m;x;x>>=1) ans+=mn[s]; return ans; }
什麼?zkw線段樹的區間查詢?我不會啊。
那麼這裏的區間查詢...其實有點難說啊!要不就直接上代碼得了?咳咳...
這個其實和上面的區間更新的思路差很少,可能要講的就是標記累加的問題了吧。
那麼 lc 和 rc 以前已經講過了,就是 s 節點和 t 節點分別走過的節點中所包含的更新區間的長度。
那麼 add 這個數組啊...啊...啊...這個數組啊,它...要不咱們直接看代碼吧?
它好在哪裏啊?好難說啊...其實它就是記錄了你每次大塊累加區間時的副產品啊,相似於線段樹的懶標記。
可是和普通線段樹不同的是,線段樹的查詢是自上而下查詢(順便釋放標記)而後又自下而上的遞歸回去的,
而 zkw 的查詢是直接自下而上的,因而它沒法釋放標記,因而它就在遇到某個打過懶標記的節點時,將當前查詢到的區間長度乘上標記值,累加入答案。
(因此這仍是懶標記啊!不上圖了自行腦補。emmm...算了吧那仍是上一張圖好了)
inline int query_sum(int s,int t){ int lc=0,rc=0,len=1,ans=0; for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ if(s&1^1) ans+=sum[s^1]+len*add[s^1],lc+=len; if(t&1) ans+=sum[t^1]+len*add[t^1],rc+=len; if(add[s>>1]) ans+=add[s>>1]*lc; if(add[t>>1]) ans+=add[t>>1]*rc; } for(lc+=rc,s>>=1;s;s>>=1) if(add[s]) ans+=add[s]*lc; return ans; } inline int query_min(int s,int t,int L=0,int R=0,int ans=0){ if(s==t) return query_node(s); // 單點要特判, 下同 for(s+=m,t+=m;s^t^1;s>>=1,t>>=1){ // 這裏 s 和 t 直接加上 m L+=mn[s],R+=mn[t]; if(s&1^1) L=min(L,mn[s^1]); if(t&1) R=min(R,mn[t^1]); } for(ans=min(L,R),s>>=1;s;s>>=1) ans+=mn[s]; return ans; } inline int query_max(int s,int t,int L=0,int R=0,int ans=0){ if(s==t) return query_node(s); for(s+=m,t+=m;s^t^1;s>>=1,t>>=1){ L+=mx[s],R+=mx[t]; if(s&1^1) L=max(L,mx[s^1]); if(t&1) R=max(R,mx[t^1]); } for(ans=max(L,R),s>>=1;s;s>>=1) ans+=mx[s]; return ans; }
這裏詢問時 s 和 t 不能 -1 或 +1 ,否則會查詢到旁邊不相干的節點。
而後 s == t 的狀況要特判一下,防止 s 和 t 一直都不是兄弟,陷入死循環。
1 //by Judge 2 #include<cstdio> 3 #include<iostream> 4 using namespace std; 5 const int M=1e5+111; 6 //#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 7 char buf[1<<21],*p1=buf,*p2=buf; 8 inline int read(){ 9 int x=0,f=1; char c=getchar(); 10 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 11 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 12 } 13 char sr[1<<21],z[20];int C=-1,Z; 14 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 15 inline void print(int x){ 16 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 17 while(z[++Z]=x%10+48,x/=10); 18 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 19 } 20 int n,m,q; 21 int sum[M<<2],mn[M<<2],mx[M<<2],add[M<<2]; 22 inline void build(){ 23 for(m=1;m<=n;m<<=1); 24 for(int i=m+1;i<=m+n;++i) 25 sum[i]=mn[i]=mx[i]=read(); 26 for(int i=m-1;i;--i){ 27 sum[i]=sum[i<<1]+sum[i<<1|1]; 28 mn[i]=min(mn[i<<1],mn[i<<1|1]), 29 mn[i<<1]-=mn[i],mn[i<<1|1]-=mn[i]; 30 mx[i]=max(mx[i<<1],mx[i<<1|1]), 31 mx[i<<1]-=mx[i],mx[i<<1|1]-=mx[i]; 32 } 33 } 34 inline void update_node(int x,int v,int A=0){ 35 x+=m,mx[x]+=v,mn[x]+=v,sum[x]+=v; 36 for(;x>1;x>>=1){ 37 sum[x]+=v; 38 A=min(mn[x],mn[x^1]); 39 mn[x]-=A,mn[x^1]-=A,mn[x>>1]+=A; 40 A=max(mx[x],mx[x^1]), 41 mx[x]-=A,mx[x^1]-=A,mx[x>>1]+=A; 42 } 43 } 44 inline void update_part(int s,int t,int v){ 45 int A=0,lc=0,rc=0,len=1; 46 for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ 47 if(s&1^1) add[s^1]+=v,lc+=len, mn[s^1]+=v,mx[s^1]+=v; 48 if(t&1) add[t^1]+=v,rc+=len, mn[t^1]+=v,mx[t^1]+=v; 49 sum[s>>1]+=v*lc, sum[t>>1]+=v*rc; 50 A=min(mn[s],mn[s^1]),mn[s]-=A,mn[s^1]-=A,mn[s>>1]+=A, 51 A=min(mn[t],mn[t^1]),mn[t]-=A,mn[t^1]-=A,mn[t>>1]+=A; 52 A=max(mx[s],mx[s^1]),mx[s]-=A,mx[s^1]-=A,mx[s>>1]+=A, 53 A=max(mx[t],mx[t^1]),mx[t]-=A,mx[t^1]-=A,mx[t>>1]+=A; 54 } 55 for(lc+=rc;s;s>>=1){ 56 sum[s>>1]+=v*lc; 57 A=min(mn[s],mn[s^1]),mn[s]-=A,mn[s^1]-=A,mn[s>>1]+=A, 58 A=max(mx[s],mx[s^1]),mx[s]-=A,mx[s^1]-=A,mx[s>>1]+=A; 59 } 60 } 61 inline int query_node(int x,int ans=0){ 62 for(x+=m;x;x>>=1) ans+=mn[x]; return ans; 63 } 64 inline int query_sum(int s,int t){ 65 int lc=0,rc=0,len=1,ans=0; 66 for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ 67 if(s&1^1) ans+=sum[s^1]+len*add[s^1],lc+=len; 68 if(t&1) ans+=sum[t^1]+len*add[t^1],rc+=len; 69 if(add[s>>1]) ans+=add[s>>1]*lc; 70 if(add[t>>1]) ans+=add[t>>1]*rc; 71 } 72 for(lc+=rc,s>>=1;s;s>>=1) if(add[s]) ans+=add[s]*lc; 73 return ans; 74 } 75 inline int query_min(int s,int t,int L=0,int R=0,int ans=0){ 76 if(s==t) return query_node(s); 77 for(s+=m,t+=m;s^t^1;s>>=1,t>>=1){ 78 L+=mn[s],R+=mn[t]; 79 if(s&1^1) L=min(L,mn[s^1]); 80 if(t&1) R=min(R,mn[t^1]); 81 } 82 for(ans=min(L,R),s>>=1;s;s>>=1) ans+=mn[s]; 83 return ans; 84 } 85 inline int query_max(int s,int t,int L=0,int R=0,int ans=0){ 86 if(s==t) return query_node(s); 87 for(s+=m,t+=m;s^t^1;s>>=1,t>>=1){ 88 L+=mx[s],R+=mx[t]; 89 if(s&1^1) L=max(L,mx[s^1]); 90 if(t&1) R=max(R,mx[t^1]); 91 } 92 for(ans=max(L,R),s>>=1;s;s>>=1) ans+=mx[s]; 93 return ans; 94 } 95 96 signed main(){ 97 98 99 100 101 102 return 0; 103 }
板子題?這個真沒有...(不過你能夠拿普通線段樹的板子題等練手)
默默放上線段樹板子題的連接...
1. 線段樹 1
2. 線段樹 2
1.
1 //by Judge 2 #include<cstdio> 3 #include<iostream> 4 #define ll long long 5 using namespace std; 6 const int M=1e5+111; 7 //#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 8 char buf[1<<21],*p1=buf,*p2=buf; 9 inline ll read(){ 10 ll x=0,f=1; char c=getchar(); 11 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 12 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 13 } 14 char sr[1<<21],z[20];int C=-1,Z; 15 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 16 inline void print(ll x){ 17 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 18 while(z[++Z]=x%10+48,x/=10); 19 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; 20 } 21 ll n,m,q; 22 ll sum[M<<2],add[M<<2]; 23 inline void build(){ 24 for(m=1;m<=n;m<<=1); 25 for(int i=m+1;i<=m+n;++i) sum[i]=read(); 26 for(int i=m-1;i;--i) sum[i]=sum[i<<1]+sum[i<<1|1]; 27 } 28 inline void update_part(int s,int t,ll v){ 29 ll A=0,lc=0,rc=0,len=1; 30 for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ 31 if(s&1^1) add[s^1]+=v,lc+=len; 32 if(t&1) add[t^1]+=v,rc+=len; 33 sum[s>>1]+=v*lc,sum[t>>1]+=v*rc; 34 } for(lc+=rc,s>>=1;s;s>>=1) sum[s]+=v*lc; 35 } 36 inline ll query_sum(int s,int t){ 37 ll lc=0,rc=0,len=1,ans=0; 38 for(s+=m-1,t+=m+1;s^t^1;s>>=1,t>>=1,len<<=1){ 39 if(s&1^1) ans+=sum[s^1]+len*add[s^1],lc+=len; 40 if(t&1) ans+=sum[t^1]+len*add[t^1],rc+=len; 41 if(add[s>>1]) ans+=add[s>>1]*lc; 42 if(add[t>>1]) ans+=add[t>>1]*rc; 43 } for(lc+=rc,s>>=1;s;s>>=1) if(add[s]) ans+=add[s]*lc; 44 return ans; 45 } 46 signed main(){ 47 n=read(),q=read(),build(); 48 int opt,x,y; ll k; 49 while(q--){ 50 opt=read(),x=read(),y=read(); 51 if(opt&1) k=read(),update_part(x,y,k); 52 else print(query_sum(x,y)); 53 } Ot(); return 0; 54 }
2.
emmm...實在是太晚啦(實際上是沒有研究過區間乘),因此就...您就自個兒研究吧~~~
題目: 無聊的數列
其實這道題用普通線段樹 + 懶標記也能夠作 (你能夠試試?)
可是用了 zkw 以後...那個代碼量的差異,我都不想說什麼...(誒?貌似普通線段樹用了標記永久化以後差很少也是這個碼量?)
1 //by Judge 2 #include<cstdio> 3 #include<iostream> 4 using namespace std; 5 const int M=1<<20; 6 int n,m,q,opt,L,R,k,d; 7 int a[M],lt[M],dt[M]; 8 //#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) 9 char buf[1<<21],*p1=buf,*p2=buf; 10 inline int read(){ 11 int x=0,f=1; char c=getchar(); 12 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 13 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; 14 } 15 char sr[1<<21],z[20];int C=-1,Z; 16 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} 17 inline void print(int x){ 18 if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; 19 while(z[++Z]=x%10+48,x/=10); 20 while(sr[++C]=z[Z],--Z);sr[++C]='\n'; Ot(); 21 } 22 inline void build(){ 23 for(int i=m;i;--i) lt[i]=lt[i<<1]; 24 } 25 inline void update(int L,int R,int k,int d){ //update 仍是蠻常規的 26 for(int l=L+m-1,r=R+m+1;l^r^1;l>>=1,r>>=1){ 27 if(l&1^1) a[l^1]+=k+(lt[l^1]-L)*d,dt[l^1]+=d; 28 if(r&1) a[r^1]+=k+(lt[r^1]-L)*d,dt[r^1]+=d; 29 } 30 } 31 inline int query(int p,int res){ //query 感性理解一下:非葉子節點存儲的是附加值,也就是操做 1 當中加入的等差數列 32 for(int i=m+p;i;i>>=1) res+=a[i]+(p-lt[i])*dt[i]; 33 return res; 34 } 35 int main(){ 36 n=read(),q=read(); for(m=1;m<=n;m<<=1); printf("%d\n",m); 37 for(int i=1;i<=n;++i) a[m+i]=read(),lt[m+i]=i; 38 build(); 39 while(q--){ 40 opt=read(); 41 if(opt&1) L=read(),R=read(),k=read(),d=read(),update(L,R,k,d); 42 else k=read(),print(query(k,0)); 43 } Ot(); return 0; 44 }
最後推薦一下: 某位大佬的 blog (寫的也蠻詳細的但沒我詳細,emmm...可是他那片博客裏的區間求最值是錯的,坑!)