1. 程式人生 > 其它 >Segment tree beats 學習筆記

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;  
}

最後是幾個操作, 相信大家打過板子都會, 程式碼就不給了。