淺談zkw線段樹(by Shine_hale)
阿新 • • 發佈:2018-12-30
說我 sca can 心理 www. node -- mes 處理
線段樹嘛,很好用的數據結構處理方法但是有個缺點
代碼長,不好理解,但是很強大
其建樹方法是遞歸建樹,調用棧來運行,從上至下,有人說,這類似一個回溯的過程
其實也不然,標記下放後,標記仍需上浮,一上一下,自然速度會很大的降低
那麽有沒有從下而上的操作呢?
zkw神犇出現了,“哈哈,我會”
zkw線段樹從此誕生了,zkw線段樹有很多用途,正在被開發,適用範圍沒有普通線段樹廣,但是其處理單標記問題,是比普通線段樹快一倍以上,甚好甚好
於是蒟蒻的hale查看了各方大佬的博客,以及zkw大佬本人的2013年發表的《統計的力量》雖說我看不懂吧,總算搞懂了一點點
今天給大家講的就是初步的建樹方法,以及區間修改區間求和,嚶嚶嚶
一、
建樹原理請參加《統計的力量》,圖我就不給大家放上來了,我相信各位可以看懂至少他的建樹原理吧
直接貼上代碼了
void push_up(int p) { st[p].ans=st[ls(p)].ans+st[rs(p)].ans;} void build() { for (M=1;M<=n+1;M<<=1); for (int i=M+1;i<=M+n;i++) scanf("%lld",&st[i].ans); for (int i=M-1;i;i--) push_up(i); }
二、區間修改
zkw線段樹主要不同於普通線段樹,我認為不是他的非遞歸建樹
而是他的標記永久化以及自底往上的標記上浮原理,這才是他速度快的核心
我不會告訴你我理解這花了一天時間,嚶嚶嚶
首先你要把你的區間做成開區間
nl表示左指針走了多少了
nr表示右指針走了多少了
x表示這層的點的子樹多大
然後大家畫個圖理解一下了
還是很容易的不是嗎
void update(int l,int r,ll k) { int s=M+l-1,t=M+r+1,nl=0,nr=0,x=1; for (;s^t^1;s>>=1,t>>=1,x<<=1)//這段for包含的信息有點多,還是耐心理解一下最好 { st[s].ans+=nl*k; st[t].ans+=nr*k; if (~s&1) {st[s^1].add+=k;st[s^1].ans+=k*x;nl+=x;}//處理左指針,若左指針是左兒子,則右兒子被修改 if (t&1) {st[t^1].add+=k;st[t^1].ans+=k*x;nr+=x;}//處理右指針,若右指針是右兒子,則左兒子北修改 } for (;s;s>>=1,t>>=1)//一加到底,進行修改,防制gg { st[s].ans+=k*nl; st[t].ans+=k*nr; } }
三、區間求和
原理跟更改差不多,就不一一贅述了
直接貼代碼,大家自己多想想就好了
ll query(int l,int r) { int s=l+M-1,t=r+M+1,nl=0,nr=0,x=1; ll ans=0; for (;s^t^1;s>>=1,t>>=1,x<<=1) { if (st[s].add) ans+=st[s].add*nl; if (st[t].add) ans+=st[t].add*nr; if (~s&1) {ans+=st[s^1].ans;nl+=x;} if (t&1) {ans+=st[t^1].ans;nr+=x;} } for (;s;s>>=1,t>>=1) { ans+=st[s].add*nl; ans+=st[t].add*nr; } return ans; }
四、總結
zkw線段樹,真的好用,快捷,必要時可以考慮一下,很爽的,嚶嚶嚶
其實《統計的力量》當中後面有很多新奇的玩法,奈何hale文化課壓力太大,滾去學文化課了,望各位神犇學會後,教hale一下了
最後的最後就是貼代碼的時間了
本題原型
#include<bits/stdc++.h> typedef long long ll; using namespace std; const int Ma=200010; int m,n,k,M; ll a[Ma]; struct node { ll ans,add;} st[Ma<<1]; int ls(int p) {return p<<1;} int rs(int p) {return p<<1|1;} void push_up(int p) { st[p].ans=st[ls(p)].ans+st[rs(p)].ans;} void build() { for (M=1;M<=n+1;M<<=1); for (int i=M+1;i<=M+n;i++) scanf("%lld",&st[i].ans); for (int i=M-1;i;i--) push_up(i); } void update(int l,int r,ll k) { int s=M+l-1,t=M+r+1,nl=0,nr=0,x=1; for (;s^t^1;s>>=1,t>>=1,x<<=1)//這段for包含的信息有點多,還是耐心理解一下最好 { st[s].ans+=nl*k; st[t].ans+=nr*k; if (~s&1) {st[s^1].add+=k;st[s^1].ans+=k*x;nl+=x;}//處理左指針,若左指針是左兒子,則右兒子被修改 if (t&1) {st[t^1].add+=k;st[t^1].ans+=k*x;nr+=x;}//處理右指針,若右指針是右兒子,則左兒子北修改 } for (;s;s>>=1,t>>=1)//一加到底,進行修改,防制gg { st[s].ans+=k*nl; st[t].ans+=k*nr; } } ll query(int l,int r) { int s=l+M-1,t=r+M+1,nl=0,nr=0,x=1; ll ans=0; for (;s^t^1;s>>=1,t>>=1,x<<=1) { if (st[s].add) ans+=st[s].add*nl; if (st[t].add) ans+=st[t].add*nr; if (~s&1) {ans+=st[s^1].ans;nl+=x;} if (t&1) {ans+=st[t^1].ans;nr+=x;} } for (;s;s>>=1,t>>=1) { ans+=st[s].add*nl; ans+=st[t].add*nr; } return ans; } int main() { int x,y;ll z; scanf("%d%d",&n,&m); build(); for (int i=1;i<=m;i++) { scanf("%d",&k); switch(k) { case 1:{scanf("%d%d%lld",&x,&y,&z); update(x,y,z); break;} case 2:{scanf("%d%d",&x,&y); printf("%lld\n",query(x,y)); break;} } } return 0; }
淺談zkw線段樹(by Shine_hale)