目录
一.什么是树链剖分
所谓树链剖分,就是把一颗树用多条链的形式展现出来,一般搭配线段树等数据结构使用。
一般分为重链剖分,长链剖分,实链剖分三种形式,今天我来总结的是重链剖分,其主要运用于树上区间维护,求解LCA等。
二.重链剖分的重要组成
1.重儿子:对于一个节点来说,他的子节点的子节点个数最多的那个节点就是他的重儿子,如果有相等的,任取其一作为重儿子即可。
2.轻儿子:除了重儿子之外的节点全是轻儿子
3.重链:全部都是重儿子的一条链
4.轻链:除了重链的链
上图!!!(红色节点为重儿子,蓝色条表示重链)
三.实现步骤
1.预处理出重儿子,深度,以及父亲节点
2.剖分重链,首先从重儿子开始遍历
步骤一代码实现
//预处理重儿子
void dfs1(int cur,int fa)
{
dep[cur] = dep[fa] + 1;
siz[cur] = 1;
fat[cur] = fa;
for(int i = he[cur]; ~i;i = e[i].nt) {
int j = e[i].to;
if(j == fa) continue;
dfs1(j,cur);
siz[cur] += siz[j];
//让子节点个数最多的作为当前节点的重儿子
if(siz[j] > siz[son[cur]]) son[cur] = j;
}
}
步骤二代码实现
//剖分重链
void dfs2(int cur,int fa)
{
dfn[cur] = ++ tim;
top[cur] = fa;
//按照重链顺序重新构建数组
wei[tim] = a[cur];
if(!son[cur]) return ;
//从重儿子开始走
dfs2(son[cur],fa);
for(int i = he[cur]; ~i;i = e[i].nt) {
int j = e[i].to;
if(j == fat[cur] || j == son[cur]) continue;
dfs2(j,j);
}
}
四.应用
1.实现某个节点的子树全部加上一个x
2.查询某个节点的子树和
3.实现树上任意两点的简单路径全部加上x
4.查询任意两点的简单路径的和
1.对于第一个操作
我们发现一个子树的dfs序永远是一段连续的,因此我们只需要让这个子树的父亲节点-子树父亲节点+子树大小-1这个区间加上一个x即可。
2.对于第二个操作
跟第一个操作类似,区间一样。
3. 对于第三个操作
不同链上操作,首先得跳到同一跳重链,跳的同时操作当前重链。跳的规则是:让两个节点中重链父亲的深度最大的那个节点开始跳,直到跳到同一条重链上。
4.对于第四个操作
跟第三个操作类似
五.代码实现
#include "bits/stdc++.h"
using namespace std;
const int N = 100010,M = 200020;
struct Edge {
int to;
int nt;
}e[M];
struct SegTree {
int l,r;
int s,lazy;
}seg[N << 2];
int idx,mod,a[N],tim;
int dfn[N],fat[N],son[N],siz[N],he[N],wei[N],top[N],dep[N];
void add(int a,int b)
{
e[++idx].nt = he[a];
e[idx].to = b;
he[a] = idx;
}
void init()
{
idx = tim = 0;
memset(dfn,0,sizeof(dfn));
memset(siz,0,sizeof(siz));
memset(he,-1,sizeof(he));
memset(dep,0,sizeof(dep));
}
void push_up(int u)
{
seg[u].s = (seg[u << 1].s + seg[u << 1 | 1].s) % mod;
}
void push_down(int u)
{
seg[u << 1].s = (seg[u << 1].s + (seg[u].lazy * (seg[u << 1].r - seg[u << 1].l + 1))) % mod;
seg[u << 1].lazy = (seg[u].lazy + seg[u << 1].lazy) % mod;
seg[u << 1 | 1].s = (seg[u << 1 | 1].s + (seg[u].lazy * (seg[u << 1 | 1].r - seg[u << 1 | 1].l + 1))) % mod;
seg[u << 1 | 1].lazy = (seg[u].lazy + seg[u << 1 | 1].lazy) % mod;
seg[u].lazy = 0;
}
void build(int u,int l,int r)
{
if(l == r) seg[u] = {l,r,wei[l],0};
else {
seg[u] = {l,r,0,0};
int mid = (l + r) >> 1;
build(u << 1,l,mid);
build(u << 1 | 1,mid + 1,r);
push_up(u);
}
}
void modify(int u,int l,int r,int v)
{
if(seg[u].l >= l && seg[u].r <= r) {
seg[u].s = (seg[u].s + v * (seg[u].r - seg[u].l + 1)) % mod;
seg[u].lazy = (seg[u].lazy + v) % mod;
}
else {
push_down(u);
int mid = (seg[u].l + seg[u].r) >> 1;
if(l <= mid) modify(u << 1,l,r,v);
if(r > mid) modify(u << 1 | 1,l,r,v);
push_up(u);
}
}
int query(int u,int l,int r)
{
if(seg[u].l >= l && seg[u].r <= r) return seg[u].s;
else {
int s = 0,mid;
push_down(u);
mid = (seg[u].l + seg[u].r) >> 1;
if(l <= mid) s = (s + query(u << 1,l,r)) % mod;
if(r > mid) s = (s + query(u << 1 | 1,l,r)) % mod;
return s;
}
}
//预处理重儿子
void dfs1(int cur,int fa)
{
dep[cur] = dep[fa] + 1;
siz[cur] = 1;
fat[cur] = fa;
for(int i = he[cur]; ~i;i = e[i].nt) {
int j = e[i].to;
if(j == fa) continue;
dfs1(j,cur);
siz[cur] += siz[j];
if(siz[j] > siz[son[cur]]) son[cur] = j;
}
}
//剖分重链
void dfs2(int cur,int fa)
{
dfn[cur] = ++ tim;
top[cur] = fa;
wei[tim] = a[cur];
if(!son[cur]) return ;
dfs2(son[cur],fa);
for(int i = he[cur]; ~i;i = e[i].nt) {
int j = e[i].to;
if(j == fat[cur] || j == son[cur]) continue;
dfs2(j,j);
}
}
//子树加z
void moson(int x,int z)
{
modify(1,dfn[x],dfn[x] + siz[x] - 1,z);
}
//查询子树
int quson(int x)
{
return query(1,dfn[x],dfn[x] + siz[x] - 1);
}
//不同重链上的添加操作
void mochain(int x,int y,int z)
{
z %= mod;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x,y);
modify(1,dfn[top[x]],dfn[x],z);
x = fat[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
modify(1,dfn[x],dfn[y],z);
}
//不同重链上的查询操作
int quchain(int x,int y)
{
int s = 0;
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x,y);
s = (s + query(1,dfn[top[x]],dfn[x])) % mod;
x = fat[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
s = (s + query(1,dfn[x],dfn[y])) % mod;
return s;
}
int main()
{
int n,m,r;
scanf("%d%d%d%d",&n,&m,&r,&mod);
init();
for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
while(n > 1) {
int u,v;
n --;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs1(r,r);
dfs2(r,r);
//建立线段树
build(1,1,tim);
while(m --) {
int op,x,y,z;
scanf("%d",&op);
if(op == 1) {
scanf("%d%d%d",&x,&y,&z);
mochain(x,y,z);
}
else if(op == 2) {
scanf("%d%d",&x,&y);
printf("%d\n",quchain(x,y));
}
else if(op == 3) {
scanf("%d%d",&x,&z);
moson(x,z);
}
else if(op == 4) {
scanf("%d",&x);
printf("%d\n",quson(x));
}
}
return 0;
}
六.练习题
P3384 【模板】轻重链剖分/树链剖分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P2590 [ZJOI2008]树的统计 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P4315 月下“毛景树” - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)