ZKW 非递归线段树
参考文章:线段树详解(非递归版)_非递归线段树-CSDN博客
建树:
原数组[1,n] 存在线段树的 [2,n+1] (为了方便区间查询,要空出第一个节点和最后一个节点)
区间查询
若查询[L,R] 区间, 选取L-1和 R+1 两个节点, 向上寻找.
- 每次到达父节点 L-1 查看自己的父亲的右节点是否是R+1(或者自己是不是右节点),
- 如果不是则加上右节点的 val .
- R+1 节点则查看自己的父亲的左节点是否是 L-1(或者自己就是左节点),
- 如果不是 加上左节点的 val .
线段树填充
若该线段树可维护 N 个元素
如果该线段树是满二叉树, 那么会有
线段树下标 + N -1 = 存储下标
而之前为了方便区间查询有:
原数组下标 + 1 = 线段树下标
那么如何确定 N 值?
N 是大于等于n +2 的,且由于要构造满二叉树, N 是 2 的幂次方
存在Lazytag 时该怎么进行区间查询?
非递归是从下往上进行查询的
-
如果题目允许, 可以先打上标记,所有标记都打完之后一次下推所有标记, 然后再开始查询
-
如果标记和查询交错, 那么可以采用标记永久化的方法, 也就是不下推标记.
L-1 与 R+1 节点往上查询的时候, 多带上一个变量, 记录此时已经计算了多少个数,当该节点达到了一个存在 tag标记的节点的时候, 加上tag 对各个数的影响,然后一次一路寻找到根节点, 返回答案
区间修改
#define maxn 100009
int a[maxn];
int sum[maxn<<2];// 区间和
int add[maxn<<2]; // 区间加lazytag
int n; // 原数组元素
int N: // N为扩充元素个数
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
建树
void build(int n){N =1 ;while(N<n+2)N<<=1;for(int i =1 ;i<=n;i++)sum[N+i] = a[i];// 直接修改叶子节点标记, 注意因为形成满二叉树, 其实有一些节点是空节点, 为0;for(int i= N -1 ;i>0;i--){// 更新根节点sum[i] = sum[ls(i)] + sum[rs(i)];add[i] = 0;}
}
点修改
void update(int l,int c){for(int i = N+l;i>0;i>>=1){sum[i] += c;}
}
区间修改(无tag, 单点修改版本)
void change(int l ,int r){l=l+N-1,r=r+N+1;// l^s^1 == 0 就代表 l 与 s 相邻, 也就是所有区间统计的内容都已经取完了int ans =0;while(l^s^1){if(~l&1) ans += sum[l^1]; //如果l 是左节点if(r&1) ans += sum[r^1]; //如果r 是右节点l >>= 1;r >>= 1; // 寻找父亲节点}
}
注意l ^r ^1 = 0 除了存在相同的父节点还有一种情况,当然这两种情况都是可以直接退出的,可以所有打上的标记都已经统计完了:
区间修改(区间加版本)
void changeblock(int l,int r,int z){int i,j,s1=0,s2=0,x=0;for(i=l+N-1,j=r+N+1;i^j^1;i>>=1,j>>=1){sum[i] += s1 * z; sum[j] += s2 * z;if(~i&1) add[i^1] += z, sum[i^1]+= s1*z,s1+=x;// x 为包括的节点数if(j&1) add[j^1] += z , sum[j^1]+=s2*z, s2+=x;}for(;i;i>>=1,j>>=1){// 更新上层sum[s] += s1* z;sum[t] += s2* z;}
}
区间查询(tag 版本, 区间加版本)
void query(int l,int r){int ans =0,i,j,s1,s2,x=1;for(i= l+ N-1,j = l+ N +1;i^j^1;i>>=1,j>>=1,x<<=1){if(add[i]) ans += s1*add[i];if(add[j]) ans += s2*add[j];if(~i&1) ans += sum[i^1],s1+=x;if(j&1) ans += sum[j^1],s2+=x;}for(;i;i>>=1,j){if(add[i])ans+= s1 *add[i];if(add[j])ans++ s2 *add[j]}return ans ;
}