📌  相关文章
📜  细分树|设置1(给定范围的总和)(1)

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

细分树 | 设置1 (给定范围的总和)

简介

细分树,也叫做可持久化线段树,是一种数据结构,用于对一个数列进行多次询问,每次询问都要求某个区间的统计信息,如区间和、区间最大值、区间最小值等等。与普通线段树不同之处在于,细分树可以在每次修改数列时,将原来的数列不变,并生成一个新的数列,这样就可以在之前的数列中查询到之后的数列的信息。

在本文中,我们将介绍如何使用细分树来实现一种常见的需求,即计算一个数列中给定范围的总和。

示例

假设有一个长度为 N 的数列 a,我们需要支持以下两种操作:

  • add(x, v):将 a[x] 的值增加 v
  • sum(l, r):查询区间 [l, r] 的总和。

构造细分树后,我们可以对于原来的数列 a 进行多次修改和查询,并且每次修改和查询都不会影响原来的数列。

实现
数据结构

我们首先需要定义细分树中的节点结构,以及节点信息维护函数。

struct node {
    int l, r;
    ll sum;
} tr[N * 40];
int root[N], idx = 0;
inline void pushup(int p) {
    tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
}

这里我们使用一个结构体 node 存储每个节点的信息。lr 表示这个节点所表示区间的左右端点,sum 表示这个节点所表示区间的总和。root 数组则表示所有版本的根节点,idx 用于维护 tr 数组的下标。

节点的信息维护函数 pushup 则表示如何计算一个节点的信息。对于一个节点而言,它的信息可以从它的左右孩子节点推出来。在本例中,节点的信息为区间和,因此节点的信息维护函数将它的 左右孩子节点 对应的 区间和 相加,得到了该节点所表示的 区间的区间和。

修改操作

对于每一个版本的根节点,我们都可以对其进行修改操作。因此,修改操作实际上就是将原来的节点拷贝一份,然后将需要修改的部分进行修改即可。

void modify(int l, int r, int &p, int q, int x, int v) {
    p = ++idx;
    tr[p] = tr[q];
    if (l >= x && r <= x) tr[p].sum += v;
    else {
        int mid = l + r >> 1;
        if (x <= mid) modify(l, mid, tr[p].l, tr[q].l, x, v);
        else modify(mid + 1, r, tr[p].r, tr[q].r, x, v);
        pushup(p);
    }
}

如上,修改操作表示为 modify 函数。该函数需要传递一些参数:

  • lr 表示当前节点对应的区间。
  • pq 分别表示当前节点所在的版本的下标和之前一个版本的下标。
  • xv 分别表示需要修改的元素下标和修改的值。

该函数的实现方式与普通的线段树实现方式基本一致。我们首先将当前节点的值拷贝到新的节点中,然后再对需要修改的部分进行修改。如果当前节点所表示区间包含了需要修改的元素,则将节点的总和加上修改的值,否则递归地对左右子节点进行修改,并且在修改完毕后更新总和。

查询操作

查询操作实际上就是计算出一个区间 [l,r] 的总和。由于我们在维护线段树的时候已经计算出了每个节点的总和,因此可以利用这些值来加快查询的速度。

ll query(int l, int r, int p, int x, int y) {
    if (l >= x && r <= y) return tr[p].sum;
    ll res = 0;
    int mid = l + r >> 1;
    if (x <= mid) res += query(l, mid, tr[p].l, x, y);
    if (y > mid) res += query(mid + 1, r, tr[p].r, x, y);
    return res;
}

如上,查询操作表示为 query 函数。该函数需要传递一些参数:

  • lr 表示当前节点对应的区间。
  • p 表示需要查询的版本的下标。
  • xy 分别表示需要查询的区间的左右端点。

该函数的实现方式与普通的线段树实现方式基本一致。我们首先判断当前节点所表示的区间是否在需要查询的区间范围内,如果是,则返回该节点的总和。否则递归地对左右子节点进行查询,并且将两个子节点的查询结果相加,然后返回该值。

总结

细分树是一种非常有用的数据结构,可以用来解决多次查询、修改同一个数列的问题。本文介绍了如何使用细分树来实现一种常见的需求,即计算一个数列中给定范围的总和。学习了本文,您将能够更深刻地理解细分树的实现方式,并且根据自己的需要来拓展实现更多的功能。