简单dfs序 + 树链剖分
樹鏈剖分
DFS序
先來講一講DFS序是什么東西,直接上圖,方便理解。
估計巨巨們應該知道了DFS序的兩個重要的東西,in,outin,outin,out數組。
- ininin數組就是這個點進入DFS的時間。
- outoutout數組就是這個點退出DFS遞歸棧的時間。
- 這個時間要注意,當有點進入的時候才加,沒有點進入的時候不加也不減。
所以我們發現一個節點的子樹的遍歷是將,剛好是區間[in,out][in, out][in,out],所以這里我們就可以通過線段樹或者樹狀數組來維護其子樹的節點權值以及節點權值的查詢。
DFS序的簡單問題
- 操作一,對某個節點增加x的權值。
- 操作二、對某個節點以及他的子樹都增加x的權值。
- 操作三、查詢某個節點的權值。
- 操作四、查詢某個節點的子樹的節點權值和。
對于這個問題,直接對應的就是線段樹的單點、區間更新,以及單點、區間查詢,直接上一個線段樹就行。
DFS序簡單應用題目 Apple Tree
思路
簡單的dfsdfsdfs序的應用,然后用樹狀數組或者線段是維護區間就行,這里樹狀數組簡單些,直接用樹狀數組了。
代碼
// #include <bits/stdc++.h> #include <iostream> #include <cstring> #include <cstdio> #include <algorithm>using namespace std;const int N = 1e5 + 10;int a[N], tree[N], n, m, tot; int head[N], to[N << 1], nex[N << 1], cnt; int in[N], out[N];char op[10];void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++; }void update(int pos, int x) {while(pos <= n) {tree[pos] += x;pos += (-pos) & (pos);} }int query(int pos) {int ans = 0;while(pos) {// cout << pos << endl;ans += tree[pos];pos -= (-pos) & (pos);}return ans; }void dfs(int rt, int fa) {in[rt] = ++tot;for(int i = head[rt]; ~i; i = nex[i])if(to[i] != fa)dfs(to[i], rt);out[rt] = tot; }int main() {// freopen("in.txt", "r", stdin);while(scanf("%d", &n) != EOF) {memset(head, -1, sizeof head);memset(tree, 0, sizeof tree);tot = cnt = 0;int x, y;for(int i = 1; i < n; i++) {a[i] = 1;update(i, 1);scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs(1, 0);a[n] = 1;update(n, 1);scanf("%d", &m);for(int i = 0; i < m; i++) {scanf("%s %d", op, &x);if(op[0] == 'Q') printf("%d\n", query(out[x]) - query(in[x] - 1));else {if(a[x]) update(in[x], -1), a[x] = 0;else update(in[x], 1), a[x] = 1;}}}return 0; }樹鏈剖分
我們先約定一些數組的意義。
| son | 記錄當前Index節點的重兒子編號 |
| sz | 記錄當前節點的子樹節點個數 |
| dep | 記錄當前節點的深度 |
| top | 記錄當前節點所在重鏈的最頂端的節點編號 |
| rk | 記錄dfs序為Index的節點編號 |
| id | 記錄當前Index節點的dfs序編號 |
第一次DFS
void dfs1(int rt, int f) {fa[rt] = f, sz[rt] = 1;//記錄當前節點的父節點,子樹大小。dep[rt] = dep[f] + 1;//記錄當前節點的深度。for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f) continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];//更新子樹大小。if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];//如果當前節點沒有重兒子,或者出現了一個節點的子樹節點數量大于當前的重兒子的子樹節點數量,則更新重兒子。} }上一幅圖來理解dfs1更新的結果。
都是常規的更新,應該看圖就懂了,唯一可能需要稍微一點點理解的就是son了。
第二次DFS
void dfs2(int rt, int t) {id[rt] = ++tot;rk[tot] = rt;top[rt] = t;//這個更新記錄的就是當前節點所在重鏈的深度最小的節點。if(!son[rt]) return ;//如果沒有重兒子,說明是葉節點,遞歸邊界記得返回。dfs2(son[rt], t);//優先更新當前所在的重邊,for(int i = head[rt]; ~i; i = nex[i]) {//更新其字節點的重邊。if(to[i] == fa[rt] || to[i] == son[rt]) continue;dfs2(to[i], to[i]);//與重鏈間接相連的點一定是一條重鏈的top節點} }上一幅圖來理解dfs2更新的結果,紅色的圈起來的是每一條重鏈。
這張圖是樹鏈剖分的精髓,把所有的邊劃分成了,重邊和輕邊,并且所有的節點依舊持有DFS序的優秀性質,并且一條重鏈上的dfs序一定是連續的,因此我們同樣可以用線段樹等數據結構來維護和查詢區間。
這里還有一個問題就是dfs2中為什么要先進行,對其重兒子的dfs然后再對其它的兒子進行dfs。
我們要保證top點的延續性,所以我們選擇先進行重兒子的dfs。
接下來我們考慮兩種操作如何實現
一、將樹從x到y結點最短路徑上所有節點的權值和
我們的第一想法就是能不能把這兩個點,通過轉換,變成最后在同一條重鏈上。
在我們上面記錄的變量中,有fa當前節點的父節點編號,所以我們能做的大概好像只有把這個節點向上移動,并且我們每次移動可以跨越一整條重鏈,到與之相鄰的另一條重鏈,同時我們還可以通過區間和的查詢統計每次移動的花費。
從這個方向出發,我們考慮,什么時候哪個節點可以進行這樣的操作。
每一次移動的dep[top]都會變小,如果我們讓dep[top]更小的去進行這個操作,最后肯定是不可能到達,top = top的,所以我們只有一個選擇,移動dep[top]小的節點,只有這樣,這兩個節點的top才有可能會相同,
舉個例子,統計4 -> 6的最短路的節點編號和
顯然dep[top[4]]=3<dep[top[6]]=1dep[top[4]] = 3 < dep[top[6]] = 1dep[top[4]]=3<dep[top[6]]=1,所以我們移動,節點4,統計sum(id[top[4]],id[4])=4sum(id[top[4]], id[4]) = 4sum(id[top[4]],id[4])=4,此時4到了2這個位置,我們發現top[2]=top[6]top[2] = top[6]top[2]=top[6],直接利用重鏈的dfs連續性,查詢sum(id[2],id[6])=13sum(id[2], id[6]) = 13sum(id[2],id[6])=13,最后得到sumans=4+13=17sum_{ans} = 4 + 13 = 17sumans?=4+13=17
二、將樹從x到y結點最短路徑上所有節點的權值加上Z
明白了上面的操作這個就簡單了,無非是把區間查詢改成區間更新就行了
樹鏈剖分模板題
#include <bits/stdc++.h> #define mid (l + r >> 1) #define lson rt << 1, l, mid #define rson rt << 1 | 1, mid + 1, r #define ls rt << 1 #define rs rt << 1 | 1using namespace std;typedef long long ll; const int N = 2e5 + 10;ll sum[N << 2], lazy[N << 2]; int head[N], value[N], nex[N << 1], to[N << 1], cnt; int fa[N], sz[N], dep[N], id[N], rk[N], son[N], top[N], tot; int n, m, mod;void dfs1(int rt, int f) {dep[rt] = dep[f] + 1;sz[rt] = 1; fa[rt] = f;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f) continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];} }void dfs2(int rt, int t) {top[rt] = t;id[rt] = ++tot;rk[tot] = rt;if(!son[rt])return ;dfs2(son[rt], t);for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa[rt] || to[i] == son[rt]) continue;dfs2(to[i], to[i]);} }void updown(int rt, int l, int r) {if(lazy[rt]) {sum[ls] = (sum[ls] + (mid - l + 1) * lazy[rt] % mod) % mod;sum[rs] = (sum[rs] + (r - mid) * lazy[rt] % mod) % mod;lazy[ls] = (lazy[ls] + lazy[rt]) % mod;lazy[rs] = (lazy[rs] + lazy[rt]) % mod;lazy[rt] = 0;} }void build(int rt, int l, int r) {if(l == r) {sum[rt] = value[rk[l]];return ;}build(lson);build(rson);sum[rt] = (sum[ls] + sum[rs]) % mod; }ll query(int rt, int l, int r, int L, int R) {if(l >= L && r <= R) return sum[rt];updown(rt, l, r);ll ans = 0;if(L <= mid) ans += query(lson, L, R);if(R > mid) ans += query(rson, L, R);return ans; }void update(int rt, int l, int r, int L, int R, int k) {if(l >= L && r <= R) {sum[rt] = (sum[rt] + (r - l + 1) * k % mod) % mod;lazy[rt] = (lazy[rt] + k) % mod;return ;}updown(rt, l, r);if(L <= mid) update(lson, L, R, k);if(R > mid) update(rson, L, R, k);sum[rt] = (sum[ls] + sum[rs]) % mod; }void print(int rt, int l, int r) {if(l == r) {printf("%lld\n", sum[rt]);return ;}updown(rt, l, r);print(lson);print(rson); }void op1(int x, int y, int k) {while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]]) swap(x, y);update(1, 1, n, id[top[x]], id[x], k);x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);update(1, 1, n, id[x], id[y], k); }ll op2(int x, int y) {ll ans = 0;while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]]) swap(x, y);ans = (ans + query(1, 1, n, id[top[x]], id[x])) % mod;x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);ans = (ans + query(1, 1, n, id[x], id[y])) % mod;return ans % mod; }void op3(int x, int k) {update(1, 1, n, id[x], id[x] + sz[x] - 1, k); }ll op4(int x) {return query(1, 1, n, id[x], id[x] + sz[x] - 1); }void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++; }int main() {// freopen("in.txt", "r", stdin);// freopen("out.txt", "w", stdout);memset(head, -1, sizeof head), cnt = 0;int x, y, rt, op, z;scanf("%d %d %d %d", &n, &m, &rt, &mod);for(int i = 1; i <= n; i++)scanf("%d", &value[i]);for(int i = 1; i < n; i++) {scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs1(rt, 0);dfs2(rt, rt);// int fa[N], son[N], sz[N], id[N], rk[N], tp[N], dep[N], tot;// for(int i = 1; i <= n; i++)// printf("%d %d %d %d %d %d\n", fa[i], son[i], sz[i], id[i], top[i], dep[i]);build(1, 1, n);// print(1, 1, n);// puts("");for(int i = 0; i < m; i++) {scanf("%d", &op);if(op == 1) {scanf("%d %d %d", &x, &y, &z);op1(x, y, z);}else if(op == 2) {scanf("%d %d", &x, &y);printf("%lld\n", op2(x, y) % mod);}else if(op == 3) {scanf("%d %d", &x, &y);op3(x, y);}else {scanf("%d", &x);printf("%lld\n", op4(x) % mod);}}// print(1, 1, n);return 0; }[JLOI2014]松鼠的新家
其實是個模板附贈題,比洛谷模板還簡單,就一個操作,區間中每個節點 + 1,然后要注意在最后的答案中,除了第一個被訪問的點,其他的ans都要減1處理,然后就完事了。
#include <bits/stdc++.h> #define mid (l + r >> 1) #define lson rt << 1, l, mid #define rson rt << 1 | 1, mid + 1, r #define ls rt << 1 #define rs rt << 1 | 1using namespace std;typedef long long ll; const int N = 3e5 + 10;ll sum[N << 2], lazy[N << 2], ans[N]; int head[N], to[N << 1], nex[N << 1], cnt; int fa[N], son[N], top[N], sz[N], dep[N], rk[N], id[N], tot; int n, a[N];void add(int x, int y) {to[cnt] = y;nex[cnt] = head[x];head[x] = cnt++; }void dfs1(int rt, int f) {fa[rt] = f, sz[rt] = 1;dep[rt] = dep[f] + 1;for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == f) continue;dfs1(to[i], rt);sz[rt] += sz[to[i]];if(!son[rt] || sz[to[i]] > sz[son[rt]])son[rt] = to[i];} }void dfs2(int rt, int t) {top[rt] = t;id[rt] = ++tot;rk[tot] = rt;if(!son[rt]) return ;dfs2(son[rt], t);for(int i = head[rt]; ~i; i = nex[i]) {if(to[i] == fa[rt] || to[i] == son[rt]) continue;dfs2(to[i], to[i]);} }void push_down(int rt, int l, int r) {if(lazy[rt]) {lazy[ls] += lazy[rt];lazy[rs] += lazy[rt];sum[ls] += lazy[rt] * (mid - l + 1);sum[rs] += lazy[rt] * (r - mid);lazy[rt] = 0;} }void update(int rt, int l, int r, int L, int R) {if(l >= L && r <= R) {lazy[rt] += 1;sum[rt] += r - l + 1;return ;}push_down(rt, l, r);if(L <= mid) update(lson, L, R);if(R > mid) update(rson, L, R);sum[rt] = sum[ls] + sum[rs]; }void query(int rt, int l, int r) {if(l == r) {ans[rk[l]] = sum[rt];return ;}push_down(rt, l, r);query(lson);query(rson); }void update_tree(int x, int y) {while(top[x] != top[y]) {if(dep[top[x]] < dep[top[y]]) swap(x, y);update(1, 1, n, id[top[x]], id[x]);x = fa[top[x]];}if(dep[x] > dep[y]) swap(x, y);update(1, 1, n, id[x], id[y]); }int main() {// freopen("in.txt", "r", stdin);memset(head, -1, sizeof head);scanf("%d", &n);for(int i = 1; i <= n; i++)scanf("%d", &a[i]);int x, y;for(int i = 1; i < n; i++) {scanf("%d %d", &x, &y);add(x, y);add(y, x);}dfs1(1, 0);dfs2(1, 0);for(int i = 1; i < n; i++)update_tree(a[i], a[i + 1]);query(1, 1, n);for(int i = 2; i <= n; i++)ans[a[i]]--;for(int i = 1; i <= n; i++)printf("%lld\n", ans[i]);return 0; }總結
以上是生活随笔為你收集整理的简单dfs序 + 树链剖分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 喝咖啡会长斑吗
- 下一篇: 线段树优化的Dijkstra