【ACWing】245. 你能回答这些问题吗

题目地址:

https://www.acwing.com/problem/content/description/246/

给定长度为 N N N的数列 A A A,以及 M M M条指令,每条指令可能是以下两种之一: 1   x   y 1\ x\ y 1 x y,查询区间 [ x , y ] [x,y] [x,y]中的最大连续子段和,即 max ⁡ x ≤ l ≤ r ≤ y ∑ i = l r A [ i ] \max_{x≤l≤r≤y}{∑_{i=l}^r A[i]} maxxlryi=lrA[i] 2   x   y 2\ x\ y 2 x y,把 A [ x ] A[x] A[x]改成 y y y。对于每个查询指令,输出一个整数表示答案。

输入格式:
第一行两个整数 N , M N,M N,M。第二行 N N N个整数 A [ i ] A[i] A[i]。接下来 M M M行每行 3 3 3个整数 k , x , y k,x,y k,x,y k = 1 k=1 k=1表示查询(此时如果 x > y x>y x>y,请交换 x , y x,y x,y), k = 2 k=2 k=2表示修改。

输出格式:
对于每个查询指令输出一个整数表示答案。每个答案占一行。

数据范围:
N ≤ 500000 , M ≤ 100000 N≤500000,M≤100000 N500000,M100000
− 1000 ≤ A [ i ] ≤ 1000 −1000≤A[i]≤1000 1000A[i]1000

涉及到区间查询和单点修改,可以用线段树来做。考虑一下每个树节点要存什么,首先其维护的区间左右端点肯定是要的,接着,维护的区间的最大子段和肯定也是要的;如果需要计算当前区间的最大子段和,其有若干种情况,一种是该最大子段和完全包含在左右子区间中,另一种是最大子段和要横跨中点;前者只需要子区间的最大子段和即可,后者则需要左子区间的最大后缀和与右子区间的最大前缀和,这样最大子段和已经解决了。接下来的问题是,要求当前区间的最大前后缀,是否可以依靠左右子区间的相关信息求出来。这一点也可以类似讨论,当前区间的最大前缀可能是左子区间的最大前缀(未横跨中点),也可能是左子区间的和加上右子区间的最大前缀;当前区间的最大后缀类似。所以我们看出,还需要存一下维护的区间和。综上所述,我们需要在树节点里存储的信息就是:当前维护区间的左右端点,维护区间的最大前后缀和,维护区间的区间和还有维护区间的最大子段和。代码如下:

#include <iostream>
using namespace std;

const int N = 500010;
int n, m;
// w数组存输入数列,从w[1]开始存
int w[N];
struct Node {
    
    
    int l, r;
    int sum, lmax, rmax, tmax;
} tr[4 * N];

// 已知左右子区间的树节点l和r,更新当前区间的节点root
void pushup(Node &root, Node &l, Node &r) {
    
    
    root.sum = l.sum + r.sum;
    root.lmax = max(l.lmax, l.sum + r.lmax);
    root.rmax = max(r.rmax, r.sum + l.rmax);
    // 最大子段和要么完全在左右子区间内部,要么横跨中点
    root.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
}

void pushup(int u) {
    
    
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r) {
    
    
    if (l == r) tr[u] = {
    
    l, r, w[l], w[l], w[l], w[l]};
    else {
    
    
    	// 注意上面的pushup里没有更新tr[u]维护的区间左右端点,所以这里要加上去
        tr[u] = {
    
    l, r};
        int m = l + (r - l >> 1);
        build(u << 1, l, m), build(u << 1 | 1, m + 1, r);
        pushup(u);
    }
}

int modify(int u, int x, int v) {
    
    
    if (tr[u].l == x && tr[u].r == x) tr[u] = {
    
    x, x, v, v, v, v};
    else {
    
    
        int m = tr[u].l + (tr[u].r - tr[u].l >> 1);
        if (x <= m) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);

        pushup(u);
    }
}

Node query(int u, int l, int r) {
    
    
    if (l <= tr[u].l && tr[u].r <= r) return tr[u];
    
    int m = tr[u].l + (tr[u].r - tr[u].l >> 1);
    if (r <= m) return query(u << 1, l, r);
    else if (l > m) return query(u << 1 | 1, l, r);
    else {
    
    
        Node left = query(u << 1, l, r), right = query(u << 1 | 1, l, r);
        Node res;
        pushup(res, left, right);
        return res;
    }
}

int main() {
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i];
    build(1, 1, n);

    while (m--) {
    
    
        int k, x, y;
        cin >> k >> x >> y;
        if (k == 1) {
    
    
            if (x > y) swap(x, y);
            cout << query(1, x, y).tmax << endl;
        } else modify(1, x, y);
    }
    
    return 0;
}

每次操作的时间复杂度 O ( log ⁡ n ) O(\log n) O(logn),空间 O ( n ) O(n) O(n)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/115192246
245