浅谈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)的主要内容,如果未能解决你的问题,请参考以下文章

zkw线段树

zkw线段树

HDU 1166 - 敌兵布阵 - [单点修改区间查询zkw线段树]

ZKW线段树

ZKW线段树 非递归版本的线段树

数据结构-ZKW线段树 详解