📌  相关文章
📜  动态段树:带点更新的范围和的在线查询(1)

📅  最后修改于: 2023-12-03 15:22:47.003000             🧑  作者: Mango

动态段树:带点更新的范围和的在线查询

动态段树是一种动态建树的数据结构,它可以在不知道要建多少颗树的情况下,动态地接受节点的插入和删除操作,同时支持动态区间操作。本文将介绍如何使用动态段树进行带点更新的范围和在线查询。

带点更新的范围和在线查询
问题描述

给定一个长度为 $n$ 的序列 $a_i$,支持两种操作:

1.将 $a_i$ 修改为 $x$。

2.查询区间 $[l,r]$ 的和,并在其中插入一个点 $p$。

解决方法

我们可以使用动态段树维护这个序列,每个节点都代表一段区间。对于每一次插入操作,我们可以先在线段树上二分找到要插入的位置,然后在叶子节点处更新值,并将值一直更新到根节点处。

对于区间查询的操作,我们可以在动态段树上进行区间查询,查询区间内所有节点的值之和并返回,同时,将要插入的点 $p$ 也一并插入到动态段树中。

代码实现
const int MAXN = 1e5 + 5;

struct Node {
    int lson, rson;
    int sum;
} tree[MAXN * 40];

int root[MAXN], cnt;

void build(int& now, int l, int r) {
    now = ++cnt;
    if (l == r) {
        tree[now].sum = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(tree[now].lson, l, mid);
    build(tree[now].rson, mid + 1, r);
    tree[now].sum = tree[tree[now].lson].sum + tree[tree[now].rson].sum;
} 

void update(int& now, int pre, int l, int r, int x, int k) {
    now = ++cnt;
    tree[now] = tree[pre];
    if (l == r) {
        tree[now].sum += k;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) update(tree[now].lson, tree[pre].lson, l, mid, x, k);
    else update(tree[now].rson, tree[pre].rson, mid + 1, r, x, k);
    tree[now].sum = tree[tree[now].lson].sum + tree[tree[now].rson].sum;
}

int query(int now, int l, int r, int L, int R) {
    if (!now) return 0;
    if (L <= l && r <= R) return tree[now].sum;
    int mid = (l + r) >> 1, sum = 0;
    if (L <= mid) sum += query(tree[now].lson, l, mid, L, R);
    if (mid < R) sum += query(tree[now].rson, mid + 1, r, L, R);
    return sum;
}

int main() {
    int n, q;
    scanf("%d%d", &n, &q);
    build(root[0], 1, n);
    for (int i = 1; i <= n; ++i) {
        int x;
        scanf("%d", &x);
        update(root[i], root[i - 1], 1, n, i, x);
    }
    while (q--) {
        int opt;
        scanf("%d", &opt);
        if (opt == 1) {
            int x, k;
            scanf("%d%d", &x, &k);
            update(root[x], root[x - 1], 1, n, x, k - query(root[x], 1, n, x, x));
        }
        else {
            int l, r, p;
            scanf("%d%d%d", &l, &r, &p);
            printf("%d\n", query(root[r], 1, n, l, r) + p);
            update(root[r + 1], root[r], 1, n, r + 1, p);
        }
    }
    return 0;
}

build() 函数用来建立初始的线段树,update() 函数用来插入节点,query() 函数用来查询区间和。对于插入点的操作,我们需要将区间右端点的 $sum$ 值更新为 query(root[r], 1, n, l, r) + p,否则每次查询都会把点 $p$ 计算进去,导致答案错误。