資料結構們
小複習
區間最大連續和
spoj GSS3
序列操作題
codeforces ???
- l,r,x,d : 把區間 [l,r] 加上首項為 x 公差為 d 的序列
- l,r : 詢問區間和
矩形覆蓋面積計算
tioj 1224
二元搜尋樹

Naïve
binary search tree
- O(n) insert x :
順著找到可以放的位置 -
O(n) erase x :
找到它後拔掉 找到子樹中的 lower_bound( x ) ,然後把它換上來 -
O(n) query x:
在樹上(搞不好是鍊)二分(所以可能不算二分搜)搜
Tree + Heap = Treap

Treap
- O(log2n) insert x :
???? -
O(log2n) erase x :
???? - O(log2n) query x:
二分搜
Merge Split Treap
- Merge :
把一個 key 全部都小於等於 k 的 Treap
和一個 key 全部都大於等於 k 的 Treap
合併起來 - Split :
把一棵 Treap 分割成
一個 key 全部都小於 k 的 Treap 和
一個 key 全部都大於等於 k 的 Treap
- O(log2n) insert x :
把自己 split 成 key < x 和 key ≥x 的兩塊 a,b
然後合併 a,x,b -
O(log2n) erase x :
把自己 split 成 key < x 和 key ≥x 的兩塊 a,b
再把 b split 成 key ≤x 和 key > x 的兩塊 c,d
然後合併 a,c,d - O(log2n) query x:
二分搜
Merge Split Treap
Operation 1 : Merge


key ≤3
key ≥3


比較 pri
?


比較 pri

比較 pri


比較 pri


比較 pri

比較 pri


比較 pri


Operation 2 : Split
k=4

k=4


k=4



k=4




k=4




實做
unsigned ran() { static unsigned x = 19; return ++(x *= 0xdefaced); } struct node { node * left = NULL, * right = NULL; int key; unsigned pri; node(int key):key(key), pri(ran()){}; };
實做
node * merge (node* a, node* b) { if (!a)return b; if (!b)return a; if (a->pri < b->pri) { a->right = merge(a->right, b); return a; } else { b->left = merge(a, b->left); return b; } }
實做
void split(node* now, int key, node* &a, node* &b) { if (!now) { a = b = NULL; return; } if (now->key < key) { a = now; split(now->right, key, a->right, b); } else { b = now; split(now->left, key, a, b->left); } }
關於 treap 小技巧
- 其實 pri 值只有用在 merge 決定上下的時候
所以其實可以在那個時候 再決定就好
可以發現若兩棵樹的大小分別是 Sa,Sb,那 麼 a 應該要在上層的機率約是 Sa+SbSa - 如果順便維護子樹的大小,那就可以在樹上二分搜求出第 k 大的元素
- 在序列上,把 index 視為 key,則可以建出一個完整序列的 Treap,查詢區間時,只要把整格區間切下來並且維護子樹的資訊就好。可以做到線段樹支援的事情(只是比較慢....
TNFSHOJ 31
大榕樹的祈禱
維護一個序列,操作有以下
- insert p k v1,v2,v3...vk:
把 k 個數字v1,v2,v3...vk插入在第 p 個數字的後面 - delete p k:
從第 p 個數字開始連續刪除 k 個數字 - make-same p k v : 把 p 開始的連續 k 數字都改成 v
- reverse p k : 把 p 開始的連續 k 個數字倒轉
- get-sum p k : 計算從 p 開始的 k 個數字的和
- max-sum : 計算目前整個數列中最大的連續和
小例題
請維護一個集合,使得他支援兩種操作 :
- 加入一個數字 x
- 詢問目前第 k 大的數字
x≤105
操作數量 ≤105
先假設線段樹不是代表一個序列,而是值域?
小例題
請維護一個集合,使得他支援兩種操作 :
ojdl 7129
- 加入一個數字 x
- 詢問目前第 k 大的數字
# : x≤109
操作數量 ≤3⋅105
需要再開!
持久化
隕石
有一些城市 編號由 1 n
有一些科學家想採集隕石樣本,而第 i 個城市需要採集 pi 個
有 k 筆事件兩兩事件時間差為 1 且按照時間順序 li,ri,ai 表達在從 [li,ri] 都將會有 ai 顆隕石墜落
請輸出 n 行代表在第 i 座城市至少要多久才能完成樣本採集
1≤n≤300000,1≤k≤300000
如果我有 n 顆線段樹
第 i 顆線段樹紀錄了時間從 [1,i] 的所有事件,
那就對於每個 i 就可以二分搜答案了!
持久化線段樹
實作
struct node { node *left = NULL, *right = NULL; int val; };
實做
node* add(node* old, int pos, int val) { node* res = new node(old), * now = res; int l = 1, r = n, m; res->val += val; while (l < r) { m = l + r >> 1; if (pos > m) { now->right = new node(old->right); now->right->val += val; now = now->right; old = old->right; r = m; } else { now->left = new node(old->left); now->left->val += val; now = now->left; old = old->left; l = m+1; } } return res; }
實作
node* add(node* old, int pos, int val) { node* res = new node(old), * now = res; int l = 1, r = n, m; res->val += val; while (l < r) { m = l + r >> 1; if (pos > m) { now->right = new node(old->right); now->right->val += val; now = now->right; old = old->right; r = m; } else { now->left = new node(old->left); now->left->val += val; now = now->left; old = old->left; l = m+1; } } return res; }
例題
POJ2104
靜態區間第 K 大
TIOJ 1975
有兩種工作,總共有 n 個
第一種會佔用一個處理器 使用從 li 到 ri 的所有時間 並且從頭到尾只能使用同一個處理器
第二種有完成期限,可以分配給不同的處理器,必須在 di之前完成,需要花 wi 的時間
請問至少需要幾個處理器 才能完成所有工作
NOTE : 一個處理器在任何時刻都只能處理一個工作
第二種工作在任何時刻也最多只能被一個處理器處理
li≤ ri≤106
n≤105
TIOJ 1840
帶修改區間 K 大
BIT +
動態開點值域線段樹
struct bit { node* root[maxn]; void add(int x, int y, int val) { for (;x <= maxn;x += x & -x) modify(root[x], y, val); } };
struct bit{ void shift(vector<node*> &nodes, int dir) { static const l_type = 0; for (auto &i : nodes) i = (dir == l_type ? i->left : i->right); } int left_sum(vector<node*> &nodes) { int res = 0; for (auto i : nodes) res += i->left->cnt; return res; } int query(int l, int r, int k) { vector<node*> lnode, rnode; for (int i = l;i;i ^= i & -i) lnode.pb(root[i]); for (int i = r;i;i ^= i & -i) rnode.pb(root[i]); int L = 1, R = n, M; while (L < r) { M = L + R >> 1; if (int lcnt = left_sum(rnode) - left_sum(lnode) ; lcnt >= k) { shift(rnode, 0), shift(lnode, 0); R = M; } else { k -= lcnt; shift(rnode, 1), shift(lnode, 1); L = M+1; } } return L; } };
最長遞增子序列 LIS
map 維護單調隊列
dpi:= 以數值 i 結尾最長的答案
用 map<int,int> dp;
有 q,(q≤105) 筆操作
- 加進一筆資料 x,y,w,(x≤105,y≤105,w≤109)
- 詢問 a,b :
輸出在所有資料中 x≤a,y≤b 中最大的 w
最後一題
在碼頭上,你擁有一個碼頭,根據政府規定,一個碼頭一天最多只能有兩艘船停靠卸貨,你手上有一份名單上面有
n≤105 個資訊,告訴你今天所有船的
停靠時間,離開時間,以及獲利
li,ri,ci,(li≤ri≤105,ci≤109)
在任意時刻,一個碼頭最多都只能停靠一艘船,你可以選擇要不要讓他停靠,請問你今天最多能夠獲利多少呢?
資料結構們
By Kevin Zhang
資料結構們
- 1,257