浅谈zkw线段树(by Shine_hale)
Posted hale522520
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈zkw线段树(by Shine_hale)相关的知识,希望对你有一定的参考价值。
线段树嘛,很好用的数据结构处理方法但是有个缺点
代码长,不好理解,但是很强大
其建树方法是递归建树,调用栈来运行,从上至下,有人说,这类似一个回溯的过程
其实也不然,标记下放后,标记仍需上浮,一上一下,自然速度会很大的降低
那么有没有从下而上的操作呢?
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 ",query(x,y)); break;} } } return 0; }
以上是关于浅谈zkw线段树(by Shine_hale)的主要内容,如果未能解决你的问题,请参考以下文章