洛谷 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\}\)
所以我們需要維護區間和 \(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;
}