You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

动态开点线段树

我们发现,线段树的一次操作只与一个链有关,有一些节点可能是从头到尾都用不到的。所以我们可以在建树时,不开满 4×n 的空间,而是在使用这个点的时候才建立这个点。

例如下面这张图:

我们首先存 2 ,先从根节点 root 开始查找。

发现 215 中,我们发现并没有这个点,那么建立。

发现 213 中,我们发现并没有这个点,那么建立。

发现 212 中,我们发现并没有这个点,那么建立。

建立点 2

接着存 3

发现 315 中,我们发现已经有这个点,不进行操作。

发现 313 中,我们发现已经有这个点,不进行操作。

建立点 3

以此类推。

最后我们发现,点 1,点 4,点 6 没有被建立。这是数据小的情况,如果数据规模很大,省下的空间是非常可观的。

动态开点适用于什么问题

动态开点一般在 n 很大(例如 n≤10^9)的时候使用。

带累加懒标记的动态开点线段树模板

// 动态开点线段树
#define int long long
#define ls tr[u].l
#define rs tr[u].r
#define mid ((l + r) >> 1)
struct Node {
    int l, r;
    int sum, add;
} tr[N << 1];

int root, idx;
// 汇总统计信息
void pushup(int u) {
    tr[u].sum = tr[ls].sum + tr[rs].sum;
}
// 创建节点:节点号分配,懒标记初始化
void build(int &u) {
    if (u) return;
    u = ++idx;
    // tr[u].add = 0;
}

void pushdown(int &u, int l, int r) {
    if (tr[u].add == 0) return; // 如果没有累加懒标记,返回
    build(ls);                  // 左儿子创建
    build(rs);                  // 右儿子创建
    // 懒标记下传
    tr[ls].sum += tr[u].add * (mid - l + 1); // 区间和增加=懒标记 乘以 区间长度
    tr[rs].sum += tr[u].add * (r - mid);
    tr[ls].add += tr[u].add; // 加法的懒标记可以叠加
    tr[rs].add += tr[u].add;
    // 清除懒标记
    tr[u].add = 0;
}

// 区间修改
void modify(int &u, int l, int r, int L, int R, int v) {
    build(u); // 动态开点

    if (l >= L && r <= R) {           // 如果区间被完整覆盖
        tr[u].add += v;               // 加法的懒标记可以叠加
        tr[u].sum += v * (r - l + 1); // 区间和增加=懒标记 乘以 区间长度
        return;
    }
    if (l > R || r < L) return; // 如果没有交集

    // 下传懒标记
    pushdown(u, l, r);
    // 分裂
    modify(ls, l, mid, L, R, v), modify(rs, mid + 1, r, L, R, v);
    // 汇总
    pushup(u);
}

// 区间查询
int query(int u, int l, int r, int L, int R) {
    if (l >= L && r <= R) return tr[u].sum; // 如果完整命中,返回我的全部
    if (l > R || r < L) return 0;           // 如果与我无关,返回0
    pushdown(u, l, r);
    return query(ls, l, mid, L, R) + query(rs, mid + 1, r, L, R);
}

递归进入左右儿子,如果要用新点,就开新点。

Q1:如果不动态开点是不是离散化一下就行? :理论上可以,但是有的时候强制在线,就不能离散化了

Q2:为什么一定要传递引用呢?不传递引用为什么出错呢? :这是因为你向下传递ls时,其实ls可能还没创建,如果按\&地址传递u,那么一旦ls被创建新节点号时,会回写tr[u].l,建立起链条式关系。 如果你不用\&地址符传递u,那么即使 ls被成功创建了新的节点号,但是不知道是谁的左儿子,也就没法回写tr[u].l,无法建立起链条关系。

练习题

P3372 【模板】线段树1

【黄海总结的最为完整的线段树+懒标记,动态开点线段树+懒标记的模板,十分经典,强烈推荐】 打磨一个模板完美后,开始对以前做过的习题全面修改一遍,OI这东西太耗时间了。

CF915E Physical Education Lessons

T125847 【模板】动态开点线段树

【懒标记线段树,动态+静态线段树实现】

P3373【模板】线段树2

【双懒标记线段树,动态+静态线段树实现】

P3960 列队(动态开点线段树+主席树,未能理解,待研究) https://www.cnblogs.com/LawrenceSivan/p/14686207.html