首先声明:“剖” 读 “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; }
(坏笑)