首先聲明:「剖」 讀 「pou」 !!!!!解剖的剖(pou)!!!!!
真不知道爲何他們都讀 「pao」……node
樹路徑信息維護算法。ios
將一棵樹劃分紅若干條鏈,用數據結構去維護每條鏈,複雜度爲O(logN)。c++
其實本質是一些數據結構/算法在樹上的推廣算法
—————— 360百科數據結構
常見的路徑剖分的方法是輕重樹鏈剖分(啓發式剖分)ui
將樹中的邊分爲:輕邊和重邊 定義size(X)爲以X爲根的子樹的節點個數。 令V爲U的兒子節點中size值最大的節點,那麼邊(U,V)被稱爲重邊,樹中重邊以外的邊被稱爲輕邊。spa
性質:輕邊(U,V),size(V)<=size(U)/2。 從根到某一點的路徑上,不超過O(logN)條輕邊,不超過O(logN)條重路徑。code
——————360百科blog
感謝360百科的友情支持(鼓掌)get
每一個節點都有一個重兒子,定義爲:每一個節點的最大子樹的根 。
輕兒子定義:除了重兒子其他都是輕兒子 。
重邊定義:重兒子與它爸爸連起來的邊 。
輕邊定義:除了重邊全是輕邊 。
重鏈定義:由重邊組成的鏈 。
輕鏈定義:由輕邊組成的鏈 。
給個圖:
我天這什麼玩意兒 號是我隨便編的
毫無疑問,2 是 1 的重兒子,4 是 2 的重兒子,9 是 3 的重兒子,
而 6 , 7 , 8 都能夠是 4 的重兒子(重兒子只能有一個,因此只要在 6,7,8 中隨便選一個就能夠了)。
劃分完大概是這個亞子:
其中紫色的邊爲重邊,綠色的點爲重兒子。
分爲三個大塊:
大法師一(
dfs1
):用於求每一個點的父親節點,重兒子,該點深度, 該(子)樹大小 。大法師二(
dfs2
):用於求每一個點的 dfs 序,dfs 序對應着哪一個點,該點所在鏈的頂端 。宿命(
sum
):能夠用於樹的兩點之間求和。嫦娥(
change
):能夠用於區間修改等等等等。
dfs1
):聲明一下:
ft[i] 爲 i 的父親;
son[i] 爲 i 的重兒子;
dep[i] 爲 i 的深度;
size[i] 爲 以 i 爲根的子樹的大小。
用代碼說:
void dfs1(int now, int fa, int de) { // now 爲如今所在的點,fa 爲 now 的父親,de 爲該點深度。 int v; ft[now] = fa; // 記錄父親 dep[now] = de; // 記錄深度 size[now] = 1; // 每一個點初始大小爲 1 (由於包括它本身) for (int i = head[now]; i; i = tree[i].nxt) { v = tree[i].v; if (v != fa) { // 避免回溯回去 dfs1(v, now, de + 1); // 搜索 size[now] += size[v]; // 加上子樹的大小 if (size[v] > size[son[now]]) son[now] = v; // 記錄重兒子 } } }
dfs2
):聲明一下:
dfn[i] 爲 i 的 dfs 序;
fdn[i] 爲 dfs 序爲 i 表明的節點;(不要在乎我起名的方式)
top[i] 爲 i 點所在鏈的頂端;
用代碼說:
void dfs2(int now, int t) { // now 爲當前節點,t 爲當前節點所在鏈的頂端。 int v; top[now] = t; // 記錄頂端 dfn[now] = ++cnt; // 記錄 dfs 序 fdn[cnt] = now; // 記錄 dfs 序所表明的節點 if (son[now]) dfs2(son[now], t); // 優先遍歷重兒子,使其組成一條鏈 for (int i = head[now]; i; i = tree[i].nxt) { v = tree[i].v; if (v != ft[now] && v != son[now]) dfs2(v, v); // 而後遍歷輕兒子,輕兒子的頂端爲本身 } }
sum
):以求和爲例:
首先咱們知道,
輕重鏈剖分實際上是在數據結構的基礎上實現的,只不過加上了一些求 LCA 的元素而已,
那麼,是哪個數據結構呢?
讓咱們掌聲有請:
沒錯,就是la個一調調一天的 神(po)奇(lan)數(wan)據(yi)!!
沒法否定的是,線段樹確實是區間求和的很好的一種方式,考試騙分就靠它!(除了有點頭疼)
代碼來啦~~:
int sum(int x, int y) { int ans = 0; while (top[x] != top[y]) { // 若是兩點不在同一條鏈上 if (dep[top[x]] < dep[top[y]]) swap(x, y); // 保證 x 點深度比 y 大,讓 x 點往上跳。 L = dfn[top[x]], R = dfn[x]; a_l(1); ans = (ans + ans_te) % p; // 加上這條鏈的區間值 ans_te = 0; // 記得清零(或許有些人線段樹寫法跟我不同) x = ft[top[x]]; // 從這個鏈頂跳到它的父親 } if (dep[x] > dep[y]) swap(x, y); // 保持從 x 向上跳 L = dfn[x], R = dfn[y]; a_l(1); ans = (ans + ans_te) % p; ans_te = 0; return ans % p; // 被 %p 支配的痛苦 }
change
):上代碼:
void change(int x, int y, int z) { // 大概跟 sum 差很少,改一改就好了 C = z; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); L = dfn[top[x]]; R = dfn[x]; c_l(1); x = ft[top[x]]; } if (dep[x] > dep[y]) swap(x, y); L = dfn[x], R = dfn[y]; c_l(1); }
#include <iostream> #include <cstdio> #include <queue> #define int long long #define MAXN 100010 #define LL long long #define M(a, b) std :: make_pair(a, b) #define F1(i, a, b) for (int i = a; i <= b; ++i) #define F2(i, a, b) for (int i = a; i >= b; --i) using namespace std; int ft[MAXN], son[MAXN], dep[MAXN], size[MAXN]; int dfn[MAXN], fdn[MAXN], top[MAXN]; int head[MAXN], node[MAXN]; int cnt = 0, js = 0, n, m, r, p; int ans_te = 0, L, R, C; struct edge { int l, r, book, len; int sum; }e[MAXN << 2]; struct Node { int v, nxt; }tree[MAXN << 1]; void add(int u, int v) { tree[++js].v = v; tree[js].nxt = head[u]; head[u] = js; } void buit(int l, int r, int k) { e[k].l = l; e[k].r = r; e[k].len = r - l + 1; if (l == r) { e[k].sum = (node[fdn[l]]) % p; return; } int mid = (l + r) >> 1; buit(l, mid, k << 1); buit(mid + 1, r, (k << 1) + 1); e[k].sum = (e[k << 1].sum + e[(k << 1) + 1].sum) % p; } void down(int k) { e[k << 1].book = (e[k << 1].book + (e[k].book) % p) % p; e[(k << 1) + 1].book = (e[(k << 1) + 1].book + (e[k].book) % p) % p; e[k << 1].sum = (e[k << 1].sum + (e[k].book * e[k << 1].len) % p) % p; e[(k << 1) + 1].sum = (e[(k << 1) + 1].sum + (e[k].book * e[(k << 1) + 1].len) % p) % p; e[k].book = 0; } void a_l(int k) { if (e[k].l >= L && e[k].r <= R) { ans_te = (ans_te + e[k].sum) % p; return; } if (e[k].book) down(k); int mid = (e[k].l + e[k].r) >> 1; if (L <= mid) a_l(k << 1); if (R > mid) a_l((k << 1) + 1); } void c_l(int k) { if (e[k].l >= L && e[k].r <= R) { e[k].sum = (e[k].sum + (C * e[k].len) % p) % p; e[k].book = (e[k].book + C) % p; return; } if (e[k].book) down(k); int mid = (e[k].l + e[k].r) >> 1; if (L <= mid) c_l(k << 1); if (R > mid) c_l((k << 1) + 1); e[k].sum = (e[k << 1].sum + e[(k << 1) + 1].sum) % p; } void dfs1(int now, int fa, int de) { int v; ft[now] = fa; dep[now] = de; size[now] = 1; for (int i = head[now]; i; i = tree[i].nxt) { v = tree[i].v; if (v != fa) { dfs1(v, now, de + 1); size[now] += size[v]; if (size[v] > size[son[now]]) son[now] = v; } } } void dfs2(int now, int t) { int v; top[now] = t; dfn[now] = ++cnt; fdn[cnt] = now; if (son[now]) dfs2(son[now], t); for (int i = head[now]; i; i = tree[i].nxt) { v = tree[i].v; if (v != ft[now] && v != son[now]) dfs2(v, v); } } int sum(int x, int y) { int ans = 0; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); L = dfn[top[x]], R = dfn[x]; a_l(1); ans = (ans + ans_te) % p; ans_te = 0; x = ft[top[x]]; } if (dep[x] > dep[y]) swap(x, y); L = dfn[x], R = dfn[y]; a_l(1); ans = (ans + ans_te) % p; ans_te = 0; return ans % p; } void change(int x, int y, int z) { C = z; while (top[x] != top[y]) { if (dep[top[x]] < dep[top[y]]) swap(x, y); L = dfn[top[x]]; R = dfn[x]; c_l(1); x = ft[top[x]]; } if (dep[x] > dep[y]) swap(x, y); L = dfn[x], R = dfn[y]; c_l(1); } signed main() { int x, y, z, c; scanf("%lld %lld %lld %lld", &n, &m, &r, &p); F1(i, 1, n) scanf("%lld", &node[i]); F1(i, 1, n - 1) { scanf("%lld %lld", &x, &y); add(x, y); add(y, x); } dfs1(r, 0, 1); dfs2(r, r); buit(1, n, 1); F1(i, 1, m) { ans_te = 0; scanf("%lld", &c); if (c == 1) { scanf("%lld %lld %lld", &x, &y, &z); change(x, y, z); } else if (c == 2) { scanf("%lld %lld", &x, &y); printf("%lld\n", sum(x, y)); } else if (c == 3) { scanf("%lld %lld", &x, &z); C = z; L = dfn[x]; R = dfn[x] + size[x] - 1; c_l(1); } else if (c == 4) { ans_te = 0; scanf("%lld", &x); L = dfn[x]; R = dfn[x] + size[x] - 1; a_l(1); printf("%lld\n", ans_te); } } return 0; }
(壞笑)