浅谈 zkw 线段树

这几天一直在看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;
}

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

发布了12 篇原创文章 · 获赞 1 · 访问量 906

猜你喜欢

转载自blog.csdn.net/Aloneingchild/article/details/77101697