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. 区间和

和无修区间和几乎没区别,不过也要和区间加一样过程中记录 cntlcntr
注意加上祖先贡献。

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. 其它

维护最大最小数之类的。待补。

posted @ 2025-03-24 16:52  Conan15  阅读(24)  评论(0)    收藏  举报  来源