很早以前就听说过这个算法,但是一直没怎么学习过,这一次好不容易有机会学习到了这个算法。
其实这个算法还是很简单的,只要好好学,十分钟不到就能学明白。
树链剖分,又叫轻重链剖分,是一种对树进行划分的方法。
被划分后的每个点只会属于一条链上。
这有什么作用呢?
这就相当于把一个树上问题映射到了区间上,变成了一个在一个序列上的问题。
树链剖分往往和树状数组、线段树一起使用。
下面通过引入一道例题来讲解这个算法。
一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点u的权值改为t
II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值
III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
注意:从点u到点v的路径上的节点包括u和v本身
输入文件的第一行为一个整数n,表示节点的个数。
接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。
接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。
接下来1行,为一个整数q,表示操作的总数。
接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
4
1
2
2
10
6
5
6
5
16
【数据说明】
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。
如果这是一个区间问题的话,那就是个大水题了,直接用线段树乱搞一下就可以了。
然而这是在树上的,单单使用线段树显得有些乏力,那么我们就使用树链剖分把他转化为区间问题。
下面开始讲解树链剖分。
先定义一个概念。
重儿子——就是一个结点子结点最多的儿子,连向儿子的边称为重边
轻儿子——即除了重儿子以外的儿子,连向轻儿子的边称为轻边
因此,树剖又叫轻重链剖分
该算法的核心即是:把重儿子与父亲划分到一条链上,轻儿子则自己单独开辟一条新的链。
下面来讲讲该算法的实现。
谈一谈数组的设置。
top[x]表示x结点所在的链的开始结点
s[x],即size[x]的缩写,表示x结点的子节点的个数
h[x],即x结点的深度
fa[x],即x的父亲结点
tr[x]即重儿子优先得到的dfs序
pr[x]即tr[x]的反函数
son[x]即x的重儿子
我们可以通过两遍dfs来算出这些数组
void dfs1(int x){
s[x]=1;
int i,j;
for(i=he[x];i;i=nx[i]){
j=b[i];
if (j==fa[x]) continue;
d[j]=d[x]+1;
fa[j]=x;
dfs1(j);
s[x]+=s[j];
if (!son[x]||s[son[x]]void dfs2(int x,int y){
int i;
top[pre[tr[x]=++num]=x]=y;
if (!son[x]) return;
dfs2(son[x],y);
for(i=he[x];i;i=nx[i])
if (b[i]!=fa[x]&&b[i]!=son[x]) dfs2(b[i],b[i]);
}
这个代码应该非常好理解
有了这个之后,上面的例题在套用线段树应该就可以得到解决了吧
有些毒瘤题比较恶心,用dfs很可能会爆栈,不想打人工栈的同学可以打bfs
下面提供一个bfs版本的树剖
变量名和dfs版本略有出入
void bfs(){
int l,r,i,j,t,res;
l=0,r=1,q[r]=1;
while (lx=q[++l];
for(i=he[x];i;i=nx[i]){
j=b[i];
if (j==fa[x]) continue;
fa[j]=x; h[j]=h[x]+1;
q[++r]=j;}
}
fo(i,1,n) top[i]=i,s[i]=1;
fa[1]=0;
fd(i,n,1) s[fa[q[i]]]+=s[q[i]];
dfn[1]=1; s[0]=0;
fo(i,1,n) {
x=q[i],t=0,res=dfn[x];
for(j=he[x];j;j=nx[j]) if (s[b[j]]>s[t]&&b[j]!=fa[x]) t=b[j];
if (t) son[x]=t,top[t]=top[x],dfn[t]=res+1,res+=s[t];
for(j=he[x];j;j=nx[j])
if (b[j]!=t&&b[j]!=fa[x]) dfn[b[j]]=res+1,res+=s[b[j]];}
fo(i,1,n) seq[dfn[i]]=i;
}
dfn数组相当于dfs版本的tr数组
seq数组相当于dfs版本的pre数组
最近树剖的题目做了不少,有时间的话会把博客写出来和大家一起分享
Enjoy coding life!
下面还有一道板子题
题意就是给定一棵树,给出两种操作
一种是给树中的某个点打标记
另一种是询问
询问离某个点最近的打了标记的点(一开始只有一号点打了标记)
很裸的树剖
可以用来练练熟练度和码力
#include
#include
#include
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define L rt<<1
#define R (rt<<1)+1
using namespace std;
const int N=100025;
int n,i,x,y,tot,num,seq[N],tr[N<<2],qq; char ss[20];
int nx[N<<1],b[N<<1],he[N],s[N],dfn[N],top[N],h[N],fa[N],son[N],q[N];
int read(){
int sum=0;
char c=getchar();
while (c<'0'||c>'9') c=getchar();
while (c>='0'&&c<='9'){
sum=sum*10+c-'0';
c=getchar();}
return sum;
}
inline void add(int x,int y){
nx[++tot]=he[x];he[x]=tot;b[tot]=y;
}
void bfs(){
int l,r,i,j,t,res;
l=0,r=1,q[r]=1;
while (lx=q[++l];
for(i=he[x];i;i=nx[i]){
j=b[i];
if (j==fa[x]) continue;
fa[j]=x; h[j]=h[x]+1;
q[++r]=j;}
}
fo(i,1,n) top[i]=i,s[i]=1;
fa[1]=0;
fd(i,n,1) s[fa[q[i]]]+=s[q[i]];
dfn[1]=1; s[0]=0;
fo(i,1,n) {
x=q[i],t=0,res=dfn[x];
for(j=he[x];j;j=nx[j]) if (s[b[j]]>s[t]&&b[j]!=fa[x]) t=b[j];
if (t) son[x]=t,top[t]=top[x],dfn[t]=res+1,res+=s[t];
for(j=he[x];j;j=nx[j])
if (b[j]!=t&&b[j]!=fa[x]) dfn[b[j]]=res+1,res+=s[b[j]];}
fo(i,1,n) seq[dfn[i]]=i;
}
inline void update(int rt){ tr[rt]=max(tr[L],tr[R]);}
inline void change(int rt,int l,int r,int x){
if (l==r) {
tr[rt]=l;
return;}
int mid=(l+r)>>1;
if (x<=mid) change(L,l,mid,x);
else change(R,mid+1,r,x);
update(rt);
}
inline int query(int rt,int l,int r,int x,int y){
if (l==x&&r==y) return tr[rt];
int mid=(l+r)>>1;
if (y<=mid) return query(L,l,mid,x,y);
else if (x>mid) return query(R,mid+1,r,x,y);
else return max(query(L,l,mid,x,mid),query(R,mid+1,r,mid+1,y));
}
inline void lca(int x){
int ans=0;
while (x>=1&&ans==0) {
int f1=top[x];
ans=query(1,1,n,dfn[f1],dfn[x]);
x=fa[f1];}
printf("%d\n",seq[ans]);
}
int main(){
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=read(); qq=read();
fo(i,2,n) add(x=read(),y=read()),add(y,x);
s[1]=0,fa[1]=1;
bfs();
fa[1]=1;
change(1,1,n,1);
while (qq){
qq--;
scanf("%s",ss+1); x=read();
if (ss[1]=='C') change(1,1,n,dfn[x]);
else lca(x);
}
return 0;
}