zkw 线段树学习笔记
听说有人觉得普通线段树常数大,码量大,很难调,所以写 zkw 线段树??
常数大这点我没话说,但是为什么会觉得码量大啊?
一、基本思想
把递归扔掉,快速找到叶子节点,并往上操作父亲或兄弟。
考虑到普通线段树最下面一层叶子实质上就是原数列。
假设有 $n$ 个元素,如果 $n$ 不是 $2$ 的次幂,那就在后面补,设补完之后有 $M$ 个元素。
这样线段树上总共有 $2M-1$ 个点,仍然是父子二倍编号表示法。
原序列第 $i$ 个元素(下标从 $1$ 开始),对应到树上的编号就是 $i + M$(把序列 $0$ 号元素也算进去,后面有用)。
二、简单操作
1. 建树
void build() {
int M = 1 << __lg(n) + 1;
vector<int> tr(M << 1);
for (int i = 1; i <= n; i++) tr[i + M] = a[i];
for (int i = M; i; i--) tr[i] = tr[i << 1] + tr[i << 1 | 1];
}
2. 单点加
先 $O(1)$ 找到对应的叶子编号,然后不断跳父亲修改子树和。
void add(int x, int d) { for (int i = x + M; i; i >>= 1) tr[i] += d; }
3. 单点查询
int query(int x) { return tr[x + M]; }
4. 区间和
查询 $[l, r]$ 的和,考虑改成查询 $(l, r)$ 开区间的和。
由于查询的子区间一定是连续的,所以只要每次把左右端点往父亲移动,并加上它兄弟的子树和即可。
这就要求序列实际元素下标从 $1$ 开始编号,同时前面补一个下标 $0$。
int query(int l, int r) {
int res = 0;
for ( l = l + M - 1, r = r + M + 1; l ^ r ^ 1; l >>= 1, r >>= 1) {
if (~l & 1) res += tr[l ^ 1]; // 如果 l 作为左儿子,就加上它的右兄弟子树
if ( r & 1) res += tr[r ^ 1]; // 如果 r 作为右儿子,就加上它的左兄弟子树
}
return res;
}
三、复杂操作
1. 区间加
考虑到 zkw 线段树的本质思想就是从叶子往上更新,所以不能从上往下标记下传了,否则和普通线段树没区别。
那不能下传,就考虑标记永久化啊!
和上面无标记区间和一样,改成开区间,每个点只会影响到它的兄弟。
所以给兄弟加上这个懒标记即可,但是注意兄弟还会影响祖先,所以要对左右端点记录实际上改动的区间长度。
注意:修改到 LCA 之后,还要再往上修改对应的祖先。
tag 表示的是整个子树加了多少。
void change(int l, int r, int d) {
int lenl = 0, lenr = 0, len = 1;
for (l = l + M - 1, r = r + M + 1; l ^ r ^ 1; l >>= 1, r >>= 1, len <<= 1) {
tr[l] += cntl * d, tr[r] += cntr * d;
if (~l & 1) tr[l ^ 1] += d * len, tag[l ^ 1] += d, cntl += len;
if ( r & 1) tr[r ^ 1] += d * len, tag[r ^ 1] += d, cntr += len;
}
for ( ; l ; l >>= 1, r >>= 1, len <<= 1) tr[l] += cntl * d, tr[r] += cntr * d; //记得更新公共祖先
}
2. 区间和
和无修区间和几乎没区别,不过也要和区间加一样过程中记录 cntl
和 cntr
。
注意加上祖先贡献。
int query(int l, int r) {
int res = 0, lenl = 0, lenr = 0, len = 1;
for (l = l + M - 1, r = r + M + 1; l ^ r ^ 1; l >>= 1, r >>= 1, len <<= 1) {
res += cntl * tag[l], res += cntr * tag[r];
if (~l & 1) res += tr[l ^ 1], cntl += len;
if ( r & 1) res += tr[r ^ 1], cntr += len;
}
for ( ; l ; l >>= 1, r >>= 1, len <<= 1) res += cntl * tag[l], res += cntr * tag[r];
return res;
}
3. 其它
维护最大最小数之类的。待补。