「線段樹維護棧/單調棧」學習筆記
前言
俗話說的好:
線段樹玩得好,暴踩某人陀螺Treap
線段樹玩得六,暴碾聾跌隨機數
用途
經常用於處理序列問題,這個序列經常會帶一些性質,如:
-
前面的會對后面的造成影響
-
序列必須滿足一些性質(遞增、遞減等等)
擁有線段樹的優良性質:可支持單點/區間修改、單點/區間查詢。
至於它怎么和棧扯上關系的,\(emmmm\),大概暴力可以用棧做。
特點
最為突出的特點是與平常線段樹的 \(pushup\) 差別很大,根據不同問題的描述,\(pushup\) 也各有千秋。
例題
陶陶摘蘋果
題目描述
陶陶家門前有一顆蘋果樹,樹上有 \(n\) 個蘋果,每個蘋果有不同的高度 \(h_i\),形成了一個長度為 \(n\) 的序列,之后從左往右開始摘蘋果。但是,陶陶只會摘高度嚴格遞增的蘋果,陶陶會摘多少個蘋果呢?
陶陶眼瞎,看錯了一個蘋果的高度,可能有 \(m\) 種情況,並回答這 \(m\) 種情況的答案。
輸入格式
第一行兩個整數 \(n, m\) 。
第二行有 \(n\) 個整數表示 \(h_i(1\leq h_i\leq 10^9)\) 。
接下來 \(m\) 行,每行兩個整數 \(p, h_p\),表示第 \(p\) 個位置的蘋果的實際高度是 \(h_p\) 。
輸出格式
輸出共 \(m\) 行,每行一個整數,表示這種情況下的答案。
樣例輸入
5 3
1 2 3 4 4
1 5
5 5
2 3
樣例輸出
1
5
3
簡化問題
實際上就是求一個最長的單調遞增序列,並且要支持修改。
暴力
很好想吧,從第 \(1\) 個往后掃,記一個當前的最高高度,掃到一個比它高的,答案加 \(1\),並更新最高高度。
復雜度:\(\Theta (nm)\)
仔細剖析
我們會發現:
- 一個序列左邊的值越大,右邊的值受到的限制也就越大,被統計到的元素也就越少,也就是:左邊的元素會對右邊的元素造成影響。
看一眼數據范圍:\(1e5\)
很明顯需要 \(nlog_n\) 做法,又是一個序列,又符合我們上面所說的一些性質,考慮打到線段樹上。
線段樹
既然我們將它打到了線段樹上:
- 單點修改,不考慮 \(lazy\) 標記和 \(pushdown\) 等操作。
現在我們需要思考的就是如何寫這個 \(pushup\) 。
我們可以維護一個區間最大值 \(maxval\) 。
可以發現:我們只需要求出右區間大於左區間最大值的單調遞增序列的最長長度即可。
這樣我們可以維護一個最長遞增序列長度 \(len\) 。
我們引用一個 \(Calc\) 函數,用來求出線段樹上一個節點(序列上一個區間),比 \(val\) 大的最長遞增序列長度。
分為三種情況:
-
這個區間長度為 \(1\),只需判斷這個值是不是大於 \(val\) 即可,返回長度為 \(1/0\) 。
-
左區間的最大值大於了 \(val\),那么我們只需繼續遞歸左區間,加上右兒子的 \(len\) 即可。
-
左區間的最大值小於/等於 \(val\),左區間就沒有用了,不可能會作出貢獻,我們只需繼續遞歸討論右區間即可。
inline int Calc (register int rt, register int l, register int r, register int val) { // 分別對應上面的三種情況
register int mid = (l + r) >> 1;
if (l == r) return tree[rt].maxval > val;
else if (tree[rt << 1].maxval > val) return Calc (rt << 1, l, mid, val) + tree[rt << 1 | 1].len;
else return Calc (rt << 1 | 1, mid + 1, r, val);
}
由於只會是左區間會對右區間造成影響,\(pushup\) 時,只需更新右區間的 \(len\) 即可。
inline void Pushup (register int rt, register int l, register int r) {
register int mid = (l + r) >> 1;
tree[rt].maxval = max (tree[rt << 1].maxval, tree[rt << 1 | 1].maxval);
tree[rt << 1 | 1].len = Calc (rt << 1 | 1, mid + 1, r, tree[rt << 1].maxval); // 以左區間的最大值為基准,遞歸右區間
}
考慮如何統計答案。
我們會發現,我們只記錄了每個右區間的最長遞增序列長度。
我們要求最長的遞增序列長度,可以用一下我們那個 \(Calc\) 函數,直接查詢 \(Calc (1, 1, n, -1)\) 即可。
代碼
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, m;
int a[maxn];
struct Tree {
int maxval, len;
} tree[maxn << 2];
inline int Calc (register int rt, register int l, register int r, register int val) {
register int mid = (l + r) >> 1;
if (l == r) return tree[rt].maxval > val;
else if (tree[rt << 1].maxval > val) return Calc (rt << 1, l, mid, val) + tree[rt << 1 | 1].len;
else return Calc (rt << 1 | 1, mid + 1, r, val);
}
inline void Pushup (register int rt, register int l, register int r) {
register int mid = (l + r) >> 1;
tree[rt].maxval = max (tree[rt << 1].maxval, tree[rt << 1 | 1].maxval);
tree[rt << 1 | 1].len = Calc (rt << 1 | 1, mid + 1, r, tree[rt << 1].maxval);
}
inline void Build (register int rt, register int l, register int r) {
if (l == r) {
tree[rt].maxval = a[l];
return;
}
register int mid = (l + r) >> 1;
Build (rt << 1, l, mid);
Build (rt << 1 | 1, mid + 1, r);
Pushup (rt, l, r);
}
inline void Modify (register int rt, register int l, register int r, register int pos, register int val) {
if (l == r) {
tree[rt].maxval = val;
return;
}
register int mid = (l + r) >> 1;
if (pos <= mid) Modify (rt << 1, l, mid, pos, val);
else Modify (rt << 1 | 1, mid + 1, r, pos, val);
Pushup (rt, l, r);
}
int main () {
freopen ("taopapp.in", "r", stdin);
freopen ("taopapp.out", "w", stdout);
n = read(), m = read();
for (register int i = 1; i <= n; i ++) a[i] = read();
Build (1, 1, n);
while (m --) {
register int pos = read(), val = read();
Modify (1, 1, n, pos, val);
printf ("%d\n", Calc (1, 1, n, -1));
Modify (1, 1, n, pos, a[pos]);
}
return 0;
}
P4198 樓房重建
思路
很簡單,其實跟上個題一樣。
要滿足題目中的條件,可以發現:我們要求的就是斜率嚴格遞增的最長序列。
直接套上面的板子即可。
代碼
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50, INF = 0x3f3f3f3f;
inline int read () {
register int x = 0, w = 1;
register char ch = getchar ();
for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
return x * w;
}
inline void write (register int x) {
if (x / 10) write (x / 10);
putchar (x % 10 + '0');
}
int n, m;
struct Tree {
double maxk; // 斜率
int len;
} tree[maxn << 2];
inline int Calc (register int rt, register int l, register int r, register double lastk) {
register int mid = (l + r) >> 1;
if (l == r) return tree[rt].maxk > lastk;
else if (tree[rt << 1].maxk > lastk) return Calc (rt << 1, l, mid, lastk) + tree[rt << 1 | 1].len;
else return Calc (rt << 1 | 1, mid + 1, r, lastk);
}
inline void Pushup (register int rt, register int l, register int r) {
register int mid = (l + r) >> 1;
tree[rt].maxk = max (tree[rt << 1].maxk, tree[rt << 1 | 1].maxk);
tree[rt << 1 | 1].len = Calc (rt << 1 | 1, mid + 1, r, tree[rt << 1].maxk);
}
inline void Modify (register int rt, register int l, register int r, register int pos, register int val) {
if (l == r) {
tree[rt].maxk = (double) val / pos;
return;
}
register int mid = (l + r) >> 1;
if (pos <= mid) Modify (rt << 1, l, mid, pos, val);
else Modify (rt << 1 | 1, mid + 1, r, pos, val);
Pushup (rt, l, r);
}
int main () {
n = read(), m = read();
while (m --) {
register int pos = read(), val = read();
Modify (1, 1, n, pos, val);
printf ("%d\n", Calc (1, 1, n, 0.0));
}
return 0;
}
總結
其實這個東西沒什么難的,只是根據題意來寫出相應的 \(pushup\) 操作,具體問題具體分析即可。