--- tags: algorithm --- # 線段樹 線段樹,又稱Segment tree, 簡單來說就是來存線段的 也可以方便的修改和球區間和,那要怎麼做呢 $\implies$ 分組 如果有一個區間 ```graphviz digraph main{ node[shape = rect] 1 2 3 4 } ``` 先兩兩一組 ```graphviz digraph main { node[shape = rect] "(1, 2)" -> {1 ,2} "(3, 4)" -> {3, 4} } ``` 再把兩組合併為組 ```graphviz digraph main { node[shape = rect] "(1, 4)" -> {"(1, 2)", "(3, 4)"} "(1, 2)" -> {1, 2} "(3, 4)" -> {3, 4} } ``` 可以用指標或著陣列存 用指標存實作比較直觀,一些神奇的操作也大多用指標,不過佔的記憶體比較大。 實作的話也有分`左閉又閉` 和`左閉右開`,不過為方便理解,這裡用左閉右閉來實作 ## 指標實作 ### 節點 有左節點和右節點, ```cpp = struct node { node *l, *r; node val; node(int k = 0) : val(k) {l = r = nullptr;} void pull() {val = (l ? l -> val : 0) + (r ? r -> val : 0);} } ``` --- ### 操作 #### 建樹 `void build(node *& cur, int l = 1, int r = mxN) ` 如果需要預先建樹的話還是會用陣列比較方便,不過這邊還是放個程式碼 ```cpp = void build(node *& cur, int l = 1, int r = mxN) { if(!cur) cur = new node(0); if(l == r) {cur -> val = ar[l]; return;} int m = (l + r) >> 1; build(cur -> l, l, m); build(cur -> r, m + 1, r); cur -> pull(); } ``` #### 更新 ```cpp = void pull() { val = (l ?l -> val :0) + (r ? r -> val : 0) } ``` --- #### 修改 `void upd(node *& cur, int pos, int val, int l = 1, int r = mxN)` ```cpp = void upd(node *& cur, int pos, int val, int l =1, int r = mxN) { if(!cur) cur = new node(); if(pos > r || pos < l) return; if(l == r) {cur -> val = val; return;} int m = (l + r) >> 1; upd(cur -> l, pos, val, l, m); upd(cur -> r, pos, val, m + 1, r); cur -> pull(); } ``` --- #### 查詢 `int qry(node *cur, int ql, int qr, int l = 1, int r =mxN)` ```cpp = int qry(node *cur, int ql, int qr, int l = 1, itn r = mxN) { if(!cur) return 0; if(ql <= l && r <= qr) return cur -> val; int m = (l +r) >> 1; return qry(cur -> l, ql, qr, l,, m) + qry(cur -> r, ql, qr, m + 1, r); } ``` --- ## 陣列實作 ### 操作 :::success $$lc(id) = id << 1;$$ $$rc(id) = id << 1|1;$$ ::: #### 更新 `void upd(int id)` ```cpp = void pull(int id) { seg[id] = seg[lc(id)] +seg[rc(id)] } ``` --- #### 建樹 `void build(int l = 1, int r = mxN, int id = 1)` ``` cpp = void build(int l = 1, int r = mxN, int id = 1) { if(r == l) {seg[id] = ar[l]; return;} int m = (l + r) >> 1; upd(l, m, lc(id)); upd(m +1, r, rc(id)); pull(id); } ``` --- #### 修改 `void upd(int pos, int k, int l = 1, int r = mxN, int id = 1)` ```cpp = void upd(int pos, int k, int l = 1, itn r = mxN, int id = 1) { if(pos > r || pos < l) return; if(l == r) { seg[id] = k; return; } int m = (l +r ) >> 1; upd(pos, k, l, m, lc(id)); upd(pos, k, m + 1, r, rc(id)); pull(id); } ``` --- #### 查詢 `int qry(int ql, int qr, int l = 1, int r = mxN, int id = 1)` ``` cpp = int qry(int ql, int qr, int l = 1, int r = mxN, int id = 1) { if(l > qr || r <ql) return 0; if(ql <= l && r <= qr) return seg[id]; int m = (l +r) >>1; return qry(ql, qr, l, m, lc(id)) + qry(ql, qr, m + 1, r, rc(id)); } ``` ```cpp = #define lc(id) id << 1 #define rc(id) id << 1|1 const int mxN = 1e6 + 1; int seg[mxN << 2], ar[mxN]; // mxN << 2 == mxN * 4 void build(int l = 1, int r = mxN, int id = 1) { if(l == r) {seg[id] = ar[l]; return;} int m = (l + r ) >> 1; // (l + r) / 2 build(l ,m, lc(id)); build(m + 1, r, rc(id)); seg[id] = seg[lc(id)] + seg[rc(id)]; } void upd(int pos, int val, int l = 1, int r = mxN, int id = 1) { if(pos < l || r < pos) return; if(l == r) {seg[id] = val; return;} int m = (l + r) >> 1; upd(pos, val, l, m, lc(id)); upd(pos, val, m + 1, r, rc(id)); seg[id] = seg[lc(id)] + seg[rc(id)]; } int qry(int ql, int qr, int l = 1, int r = mxN, int id = 1) { if(ql > r || qr < l) return 0; if(ql <= l && r <= qr) return seg[id]; int m = (l + r) >> 1; return qry(ql ,qr, l, m, lc(id)) + qry(ql, qr, m + 1, r, rc(id)); } ``` --- ### 複雜度 ***建樹:*** $複雜度O(N)$ ***單點修改 :*** $複雜度O(\log N)$ ***區間求值 :*** $複雜度O(\log N)$