Segment tree beats 學習筆記
模板題 :P6242
題意 : 寫一棵線段樹, 支援區間加,區間對 \(x\) 取 \(\min\), 區間和, 區間最大值, 區間歷史最大值。
直接暴力維護是 \(O(n^2)\) 的, 考慮如何優化。
考慮對於線段樹的每一個節點, 維護最大值 \(mx\), 最大值個數 \(cnt\), 嚴格次大值 \(sec\)。
在區間對 \(x\) 取 \(\min\) 時,
- 若 \(mx \leq x\), 則不會修改任何值, 直接退出.
- 若 \(x < mx\), 且 \(sec \geq x\), 則所有的 \(mx\) 變成 \(x\), 區間和加上 \(cnt \times (x - mx)\)
- 否則遞迴左右子樹.
複雜度分析 : 摘自 靈夢 的 blog :
這樣做的複雜度可以證明是 \(O(m\log n)\) 的。原論文的證明使用了勢能分析,這裡給出一種更好懂的 \(O((n+m)\log n)\) 下界的證明:
顯然只有暴力 DFS 的過程會影響複雜度,我們考慮一次區間最值操作中哪些節點會被搜尋到。發現到達的節點區間中不同的數的個數(下稱「值域」)一定會減少(因為至少將最大值與次大值合併了)。線段樹每層節點表示的區間的值域一共是 \(O(n)\) 的,一共 \(\log\) 層加起來就是 \(O(n\log n)\)。再加上每次操作的複雜度,可以得到複雜度的一個下界為 \(O((n+m)\log n)\)
。
對於此題, 因為還有區間加操作, 但是我們對 \(x\) 取 \(\min\) 時只能修改最大值的值, 相當於把維護的元素分成了 最大值 與 非最大值, 用兩個懶標記記錄即可 (程式碼中是 \(add_a\) 與 \(add_{a1}\))。
再加上歷史最大值, 我們定義 \(add_b\) 為當前區間最大值歷史上加的最多的那次的標記。
同理 \(add_{b1}\) 為當前區間非最大值歷史上加的最多的那次的標記。
一個簡單的例子是這個區間先 \(+3\), 再 \(-2\), 那麼 \(add_b\) 就應該是 \(3\)。
結合程式碼實際講一下 (由於 cnblogs 插入程式碼很鬼畜, 大家諒解一下):
struct tre {
int maxa, maxb, sec, sum, cnt, len;
int add_a, add_a1, add_b, add_b1; //最大值tag 非最大值tag 最大值歷史tag 非最大值歷史tag
} tre[M << 2];
\(pushup\) 的時候, 對於最大值在左邊或右邊或兩邊都是分類討論一下 :
inline void push_up(int p) {
tre[p].maxa = std :: max(tre[p << 1].maxa, tre[p << 1 | 1].maxa);
tre[p].maxb = std :: max(tre[p << 1].maxb, tre[p << 1 | 1].maxb);
tre[p].sum = tre[p << 1].sum + tre[p << 1 | 1].sum;
if(tre[p << 1].maxa == tre[p << 1 | 1].maxa) {
tre[p].sec = std :: max(tre[p << 1].sec, tre[p << 1 | 1].sec);
tre[p].cnt = tre[p << 1].cnt + tre[p << 1 | 1].cnt;
}
else if(tre[p << 1].maxa < tre[p << 1 | 1].maxa) {
tre[p].sec = std :: max(tre[p << 1].maxa, tre[p << 1 | 1].sec);
tre[p].cnt = tre[p << 1 | 1].cnt;
}
else {
tre[p].sec = std :: max(tre[p << 1].sec, tre[p << 1 | 1].maxa);
tre[p].cnt = tre[p << 1].cnt;
}
}
比較難理解的是下傳標記時的對答案的更改操作 :
inline void upd(int p, int v1, int v2, int v3, int v4) { //最大值 + tag, 最大值歷史 + tag, 非最大值 + tag, 非最大值歷史 + tag
tre[p].sum += v1 * tre[p].cnt + v3 * (tre[p].len - tre[p].cnt);
tre[p].maxb = std :: max(tre[p].maxb, tre[p].maxa + v2);
tre[p].add_b = std :: max(tre[p].add_b, tre[p].add_a + v2);
tre[p].add_b1 = std :: max(tre[p].add_b1, tre[p].add_a1 + v4);
tre[p].maxa += v1, tre[p].add_a += v1, tre[p].add_a1 += v3;
if(tre[p].sec != -INF) tre[p].sec += v3;
}
最後是幾個操作, 相信大家打過板子都會, 程式碼就不給了。