这几天一直在看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;
}
这里就暂时先不介绍区间了 比较难记 以后再补上