浅谈 zkw 线段树

来源:互联网 发布:西安东华软件远古 编辑:程序博客网 时间:2024/06/04 19:51

这几天一直在看zkw版的线段树
他的常数极小 代码还很短 写起来不复杂 就是一开始比较难理解
较以往的线段树写法相差很多
下面浅谈下个人对zkw版的理解

首先呢 线段树就不用讲了
线段树
这样一颗线段树 如果我们把他的标号转换成2进制 是这个样子的
很显然 设深度为m 最后一层的点数是2^(m-1)
那么对于满二叉树 总结点数易知 是2^(m-1)*2-1个
那么神奇的地方来了
我们建树直接可以这样玩

void build(int n){    for(M=1;M<n;M<<=1);    for(int i=1;i<=n;i++) cin>>tr[i+M];} 

是不是很简单 什么意思呢 我们这相当于建了一个满二叉树 根节点编号是M+i 很显然没有建完 ,还需要一步

void build(int n){    for(M=1;M<n+2;M<<=1);    for(int i=1;i<=n;i++) cin>>tr[i+M];    for(int i=M-1;i;i--) tr[i]=tr[i<<1]+tr[i<<|1]; } 

这样我们就完成了建树
1.那么如何单点修改呢?

void modify(int x,int k){    tr[x+=M]+=k;    while(x) tr[x>>=1]=tr[x<<1]+tr[x<<1|1];}

2 如何 单点查询呢?
最朴素的做法

int query(int x){    return tr[x+M];}

3好了 区间和呢?

int sum(int s,int t){    int ans=0;    for(s+=M-1,t+=M+1;s^t^1;s>>=1;t>>=1)    {        if(~s&1) ans+=tr[s^1];        if(t&1) ans+=tr[t^1];    }    return ans;} 

为什么 要这样写呢
图
我们转换成开区间后
易知 在未超过边界的情况下 若s为左儿子 那么他的兄弟一定正在查询范围内 同理t为右儿子 他兄弟也在查询范围内
那么我们就可以去维护他兄弟的值了 如此可避免重复

4区间最大最小值呢
这个时候 我们不妨改一下 存取方式 换成 差分的做法
为什么要换成差分做呢 因为这样单点话我们维护起来会更快
此时我们建树 及单点查询

void build(int n){    for(M=1;M<n+2;M<<=1);    for(int i=1;i<=n;i++) cin>>tr[i+M],    tr[i<<1]-=tr[i],tr[i<<1|1]-=tr[i];}int query(int x){    int ans=tr[x+=M];    while(x) ans+=tr[x>>=1];    return ans;}

这里就暂时先不介绍区间了 比较难记 以后再补上