📜  更新前后[L,R]中数组中元素的总和和最大值(1)

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

更新前后L,R中数组中元素的总和和最大值

在许多问题中,需要维护一个数组的总和或最大值,本文将介绍在[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]中数组元素的总和和最大值。前缀和可以用于执行一系列连续的查询,而线段树可以支持查询和更新操作。我们可以根据具体的应用情况选择恰当的方法。