📅  最后修改于: 2023-12-03 15:10:33.527000             🧑  作者: Mango
在许多问题中,需要维护一个数组的总和或最大值,本文将介绍在[L,R]范围内更新数组元素后如何高效地维护它们。我们将使用两个抽象数据类型来实现此目的:前缀和和线段树。
前缀和数组表示给定数组中所有元素从左边开始的累加和。我们可以在O(1)时间内计算出[L,R]内所有元素的总和。考虑以下代码段:
int prefix_sum[N];
for (int i = 1; i <= n; i++) {
prefix_sum[i] = prefix_sum[i-1] + a[i];
}
现在我们可以在O(1)时间内计算[L,R]范围内元素的总和:
int sum = prefix_sum[R] - prefix_sum[L-1];
显然,前缀和的空间复杂度是O(n),而时间复杂度为O(n)。这种方法很容易实现,适用于每次更新数组中所有元素的情况。
线段树是一种经典数据结构,它可以用来解决在[L,R]内维护数组元素总和和最大值的问题。我们将在叶节点中存储原数组中的元素,将中间节点存储为其子节点的总和和最大值。考虑以下代码段:
struct node {
int sum, max_value;
} t[4*N];
void build(int v, int tl, int tr) {
if (tl == tr) {
t[v].sum = a[tl];
t[v].max_value = a[tl];
} else {
int tm = (tl + tr) / 2;
build(2*v, tl, tm);
build(2*v+1, tm+1, tr);
t[v].sum = t[2*v].sum + t[2*v+1].sum;
t[v].max_value = max(t[2*v].max_value, t[2*v+1].max_value);
}
}
现在我们可以在O(logn)时间内获得[L,R]中所有元素的总和和最大值:
node query(int v, int tl, int tr, int l, int r) {
if (l > r) {
return {0, 0};
}
if (l == tl && r == tr) {
return t[v];
}
int tm = (tl + tr) / 2;
node left = query(2*v, tl, tm, l, min(r, tm));
node right = query(2*v+1, tm+1, tr, max(l, tm+1), r);
return {left.sum + right.sum, max(left.max_value, right.max_value)};
}
我们可以更新a[index]的值:
void update(int v, int tl, int tr, int index, int new_value) {
if (tl == tr) {
t[v].sum = new_value;
t[v].max_value = new_value;
} else {
int tm = (tl + tr) / 2;
if (index <= tm) {
update(2*v, tl, tm, index, new_value);
} else {
update(2*v+1, tm+1, tr, index, new_value);
}
t[v].sum = t[2*v].sum + t[2*v+1].sum;
t[v].max_value = max(t[2*v].max_value, t[2*v+1].max_value);
}
}
线段树的空间复杂度为O(n),它可以用于不同类型的查询和更新操作,它的时间复杂度为O(logn)。
本文介绍了两种方法来维护[L,R]中数组元素的总和和最大值。前缀和可以用于执行一系列连续的查询,而线段树可以支持查询和更新操作。我们可以根据具体的应用情况选择恰当的方法。