linux的基本命令(一)
主席樹
首先考慮一個比較經典的問題,你有一個靜態的數列,每次詢問一段區間 \(l \to r\) 內的第 \(k\) 小。
做法的一句話介紹,巨的人就不要往下翻了。
對於原序列的每個字首維護一顆線段樹,維護這個區間,並且這些線段樹滿足可減性。
接下具體解釋下,
考慮一個靜態區間上維護區間資訊,一般會想到什麼呢?
還記得我們在學線段樹的時候曾經是需要維護一個可修改的區間。
我們一共三種做法。
第一種,直接用 \(a[]\) 記錄,每次修改 \(O(1)\),查詢 \(O(n)\)
第二種,通過差分的思想維護字首和陣列 \(sum[]\) 每次修改 \(O(n)\) ,查詢 \(O(1)\)
第三種,則是線段樹,查詢,修改都是 \(O(log_n)\)
那麼這個問題只需要維護一段靜態區間,那麼就用差分的思想。
對於每個字首都開一顆權值線段樹,那麼這樣的話查詢就變得十分的方便。
因為只要判斷左邊的大小是否大於 \(k\) 。最裸的線段樹問題。
看似很簡單,但是空間顯然會炸。
你這麼做的空間複雜度是 \(n * n << 2\) 的。
你不炸誰炸?
那麼這個時候就可以用到動態開點了,不知道動態開點的可以去看我的 \(blog\)
這也是主席樹的靈魂部分。
記錄每個字首的根,從根開始向前一顆線段樹的子樹連邊。
具體的話是這樣實現的。
\(a[]\) 是原數列,現在處理到 \(x\) 節點。
你就依次判斷 \(mid\) 與 \(a[x]\)
如果 \(mid\) 大於 \(a[x]\) 你就把第 \(x\) 顆線段樹當前節點的右兒子連到第 \(x - 1\) 顆線段樹對應的位置。
否則就把第 \(x\) 顆線段樹當前節點的左兒子連到第 \(x - 1\) 顆線段樹對應的位置。
至於另一邊就不斷遞迴就行了,最後只需要新建一個節點就行了。
程式碼
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> #include <queue> #define N 200020 #define ls x << 1 #define rs x << 1 | 1 #define inf 0x3f3f3f3f #define inc(i) (++ (i)) #define dec(i) (-- (i)) #define mid ((l + r) >> 1) #define int long long #define XRZ 1000000003 #define debug() puts("XRZ TXDY"); #define mem(i, x) memset(i, x, sizeof(i)); #define Next(i, u) for(register int i = head[u]; i ; i = e[i].nxt) #define file(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout); #define Rep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i <= i##Limit ; inc(i)) #define Dep(i, a, b) for(register int i = (a) , i##Limit = (b) ; i >= i##Limit ; dec(i)) int dx[10] = {1, -1, 0, 0}; int dy[10] = {0, 0, 1, -1}; using namespace std; inline int read() { register int x = 0, f = 1; register char c = getchar(); while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar(); return x * f; } int n, m, T[N << 5], Data[N << 5], Left[N << 5], Right[N << 5], a[N], b[N], rt; int Build(int l, int r) { int now = inc(rt); if(l < r) Left[now] = Build(l, mid), Right[now] = Build(mid + 1, r); return now; } int Updata(int pre, int l, int r, int x) { int now = inc(rt); Left[now] = Left[pre]; Right[now] = Right[pre]; Data[now] = Data[pre] + 1; if(l >= r) return now; if(x <= mid) Left[now] = Updata(Left[pre], l, mid, x); else Right[now] = Updata(Right[pre], mid + 1, r, x); return now; } int Query(int x, int y, int l, int r, int k) { if(l == r) return l; int std = Data[Left[y]] - Data[Left[x]]; if(std >= k) return Query(Left[x], Left[y], l, mid, k); else return Query(Right[x], Right[y], mid + 1, r, k - std); } signed main() { int n = read(), m = read(); Rep(i, 1, n) a[i] = b[i] = read(); sort(b + 1, b + n + 1); int M = unique(b + 1, b + n + 1) - b - 1; T[0] = Build(1, M); Rep(i, 1, n) { a[i] = lower_bound(b + 1, b + M + 1, a[i]) - b; T[i] = Updata(T[i - 1], 1, M, a[i]); } Rep(i, 1, m) { int x = read(), y = read(), z = read(); printf("%d\n", b[Query(T[x - 1], T[y], 1, M, z)]); } return 0; }