[学习笔记]树链剖分
基本思想
一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每条边属于且只属于一条链,然后再通过数据结构来维护每一条链。
一些定义
树链:树上的路径.
剖分:把路径分类为重链和轻链.
重儿子:\(u\)的子节点中\(siz[v]\)值最大的\(v\).
轻儿子:\(u\)的其它子节点.
重边:点\(u\)与其重儿子的连边.
轻边:点\(u\)与其轻儿子的连边.
重链:由重边连成的路径.
轻链:轻边.
性质
- 如果\((u,v)\)为轻边,则\(siz[v]\;\times\;2<siz[u]\).
- 从根到某一点的路径上轻链、重链的个数都不大于\(logn\).
- 树剖序其实也可以是\(dfs\)序的一种.
实现
一些变量:
\(f[u]\)表示\(u\)的父亲.
\(siz[u]\)表示以\(u\)为根的子树的大小.
\(dep[u]\)表示\(u\)的深度(根深度为\(1\)).
\(top[u]\)表示\(u\)所在的链的顶端节点.
\(son[u]\)表示与\(u\)的重儿子.
重标号:
\(p[u]\):重标号后\(u\)的编号.
\(dfs\)序:\(dfs\)的时候先走重边.
这样可以使得重边的编号是连续的,方便维护.
用两遍\(dfs\)求出所需的所有变量以及重标号.
预处理
int f[N],p[N],dep[N],siz[N],son[N],top[N];
/*top[u]:u所在的链的顶端节点,son[u]:u的重儿子*/
inline void dfs1(int u){
int m=0;siz[u]=1;
for(int i=g[u];i;i=e[i].nxt)
if(!dep[e[i].to]){
f[e[i].to]=u;
dep[e[i].to]=dep[u]+1;
dfs1(e[i].to);
siz[u]+=siz[e[i].to];
if(siz[e[i].to]>m){
son[u]=e[i].to;
m=siz[e[i].to];
}
}
}
inline void dfs2(int u,int tp){
top[u]=tp;p[u]=++cnt;ww[cnt]=w[u];
if(son[u]) dfs2(son[u],tp);
for(int i=g[u];i;i=e[i].nxt){
if(e[i].to!=f[u]&&e[i].to!=son[u])
dfs2(e[i].to,e[i].to);
}
}
访问修改(u,v):
类似倍增的走法,每次将深度大的往上移,直到\(u,v\)属于同一条链.
inline int sum(int x,int y){
int ret=0,t;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]){
t=x;x=y;y=t;
}
ret+=ask(1,p[top[x]],p[x]);
x=f[top[x]];
}
if(p[x]>p[y]){
t=x;x=y;y=t;
}
ret+=ask(1,p[x],p[y]);
return ret;
}
inline void change(int x,int y,int k){
int t;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]){
t=x;x=y;y=t;
}
cover(1,p[top[x]],p[x],k);
x=f[top[x]];
}
if(p[x]>p[y]){
t=x;x=y;y=t;
}
cover(1,p[x],p[y],k);
}