1. 程式人生 > 其它 >洛谷 P4513:線段樹維護動態區間最大子段和

洛谷 P4513:線段樹維護動態區間最大子段和

前置知識

線段樹

線段樹是一種二叉搜尋樹,與區間樹相似,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。

使用線段樹可以快速的查詢某一個節點在若干條線段中出現的次數,時間複雜度為 \(O(log\ n)\)

如果還不會線段樹的話,建議從一些簡單的題目入手,如 洛谷 P3372 【模板】線段樹 1

最大子段和

最大子段和的定義為:給定 \(n\) 個整數(可能為負數)組成的序列 \(a_1,a_2,a_3,\ldots,a_n\),求該序列如 \(a_i,a_{i+1},\ldots,a_j\) 的子段和的最大值。

一般會使用 dp 或者遞迴解決此類問題。

題意

題目連結

給定一個數列 \(a_1,a_2,\ldots,a_n\),支援兩種操作:

操作1:求區間 \([l,r]\) 的最大子段和。

操作2:將 \(a_x\) 改為 \(k\)

解法

首先可以發現最大子段和是滿足區間加法的,所以可以想到使用線段樹來維護。

但是此題不能直接使用區間加法,舉個例子,\(\{1,-1\}\) 的最大子段和是 \(1\)\(\{-1,1\}\) 的最大子段和也是 \(1\),但是 \(\{1,-1,-1,1\}\) 的最大子段和是 \(1\),而不是直接 \(1+1=2\)

我們需要特殊的維護方法。

通過觀察可以發現,對於一個長度大於 \(1\) 的區間,它的兩個兒子的最大子段和區間可能不是連續的,如果要把兩個兒子的最大子段和加起來,必須要使左兒子的最大子段和區間靠右,右兒子的最大子段和區間靠左,如 \(\{-1,1\}\)

\(\{1,-1\}\) 就可以使用區間加法,得到 \(\{-1,1,1,-1\}\) 的最大子段和為 \(1+1=2\)

所以我們需要維護區間和 \(pre\),最大子段和 \(ans\),還有必須靠左的最大子段和 \(ml\),必須靠右的最大子段和 \(mr\)

區間和比較簡單,只需要把左兒子和右兒子加起來就可以了,於是可以得到:

\(pre_i=pre_{2i}+pre_{2i+1}\)

必須靠左的最大子段和可以由兩種狀態轉移而來,第一種是 \(ml_{2i}\),表示只選左兒子靠左部分,第二種是 \(pre_{2i}+ml_{2i+1}\) 表示左兒子全部選上,右兒子取其靠左的最大部分,所以可以得到:

\(ml_i=\max(ml_{2i},pre_{2i}+ml_{2i+1})\)

必須靠右的最大子段和同理:

\(mr_i=\max(mr_{2i+1},pre_{2i+1}+mr_{2i})\)

最大子段和由三種狀態轉移而來,第一種是 \(ans_{2i}\),表示只選左兒子的最優部分,第二種是 \(ans_{2i+1}\),表示只選右兒子的最優部分,第三種是 \(mr_{2i}+ml_{2i+1}\),表示取左兒子最優的靠右部分,加上右兒子最優的靠左部分,所以:

\(ans_i=\max(ans_{2i},ans_{2i+1},mr_{2i}+ml_{2i+1})\)

於是這道題就做完了。

程式碼

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define il inline

using namespace std;

int n, m;
int a[500005];
int opt, l, r, x, k;

#define lc (k << 1)
#define rc ((k << 1) | 1)

struct tree {
    int l, r;
    int pre, ml, mr, ans;
}t[2000005];

il void pushup (int k) {
    t[k].pre = t[lc].pre + t[rc].pre;
    t[k].ml = max(t[lc].ml, t[lc].pre + t[rc].ml);
    t[k].mr = max(t[rc].mr, t[rc].pre + t[lc].mr);
    t[k].ans = max(max(t[lc].ans, t[rc].ans), t[lc].mr + t[rc].ml);

    return;
} 

il void build (int k, int l, int r) {
    t[k].l = l, t[k].r = r;

    if (l == r) {
        t[k].pre = a[l];
        t[k].ml = a[l];
        t[k].mr = a[l];
        t[k].ans = a[l];

        return;
    }

    int mid = (l + r) >> 1;

    build(lc, l, mid);
    build(rc, mid + 1, r);

    pushup(k);
}

il void add (int k, int x, int pre) {
    if (t[k].l == t[k].r) {
        t[k].ml = pre;
        t[k].mr = pre;
        t[k].pre = pre;
        t[k].ans = pre;

        return;
    }

    int mid = (t[k].l + t[k].r) >> 1;

    if (x <= mid) {
        add(lc, x, pre);
    }

    else {
        add(rc, x, pre);
    }

    pushup(k);

    return;
}

tree ask (int k, int l, int r) {
    if (l <= t[k].l && r >= t[k].r) {
        return t[k];
    }

    int mid = (t[k].l + t[k].r) >> 1;

    if (r <= mid) {
        return ask(lc, l, r);
    }

    if (l > mid) {
        return ask(rc, l, r);
    }

    tree ls = ask(lc, l, r), rs = ask(rc, l, r), res;

    res.ml = max(ls.ml, ls.pre + rs.ml);
    res.mr = max(rs.mr, rs.pre + ls.mr);
    res.pre = ls.pre + rs.pre;
    res.ans = max(max(ls.ans, rs.ans), ls.mr + rs.ml);

    return res;
}

int main () {
    ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);

    cin >> n >> m;

    rep (i, 1, n) {
        cin >> a[i];
    }

    build(1, 1, n);

    rep (i, 1, m) {
        cin >> opt;

        if (opt == 1) {
            cin >> l >> r;

            if (l > r) {
                swap(l, r);
            }

            cout << ask(1, l, r).ans << endl;
        }

        if (opt == 2) {
            cin >> x >> k;

            add(1, x, k);
        }
    }

    return 0;
}