【算法】树链剖分
首先声明:“剖” 读 “pou” !!!!!解剖的剖(pou)!!!!!
真不知道为什么他们都读 “pao”……
进入正题:
介绍:
树路径信息维护算法。
将一棵树划分成若干条链,用数据结构去维护每条链,复杂度为O(logN)。
其实本质是一些数据结构/算法在树上的推广
—————— 360百科
大概做法:
常见的路径剖分的方法是轻重树链剖分(启发式剖分)
将树中的边分为:轻边和重边 定义size(X)为以X为根的子树的节点个数。 令V为U的儿子节点中size值最大的节点,那么边(U,V)被称为重边,树中重边之外的边被称为轻边。
性质:轻边(U,V),size(V)<=size(U)/2。 从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重路径。
——————360百科
感谢360百科的友情支持(鼓掌)
前置知识:
每个节点都有一个重儿子,定义为:每个节点的最大子树的根 。
轻儿子定义:除了重儿子其余都是轻儿子 。
重边定义:重儿子与它爸爸连起来的边 。
轻边定义:除了重边全是轻边 。
重链定义:由重边组成的链 。
轻链定义:由轻边组成的链 。
给个图:
我天这什么玩意儿 号是我随便编的
毫无疑问,2 是 1 的重儿子,4 是 2 的重儿子,9 是 3 的重儿子,
而 6 , 7 , 8 都可以是 4 的重儿子(重儿子只能有一个,所以只要在 6,7,8 中随便选一个就可以了)。
划分完大概是这个亚子:
其中紫色的边为重边,绿色的点为重儿子。
具体做法 && 算法分析:
分为三个大块:
大法师一(
dfs1
):用于求每个点的父亲节点,重儿子,该点深度, 该(子)树大小 。大法师二(
dfs2
):用于求每个点的 dfs 序,dfs 序对应着哪个点,该点所在链的顶端 。宿命(
sum
):可以用于树的两点之间求和。嫦娥(
change
):可以用于区间修改等等等等。
1. 大法师一(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; // 记录重儿子
}
}
}
2. 大法师二(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); // 然后遍历轻儿子,轻儿子的顶端为自己
}
}
3. 宿命(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 支配的痛苦
}
4. 嫦娥(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);
}
完整代码:(不多不多,只有 137 173 行)
#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;
}
给两个模板题:
(坏笑)