浅谈树链剖分
@
什么是树链剖分?
指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链,主要用来维护树上每条链的极值或和之类的。
类似
首先把树上倍增摆在前面,如果不会树上倍增就不必来看树链剖分。
为什么要学树链剖分?
有的人说:“我会树上倍增,我怕谁?”,没错,你怕的就是树链剖分。
意思是:树上倍增能做的,树链剖分都能做;树链剖分能做的,树上倍增不一定能做。树上倍增虽然能求树上区间极值或和,但一旦有树上边或点权值修改的操作,您就死翘翘了~这时我们需要一个新的算法来补上我们的树上倍增操作的漏洞,没错,就是树链剖分!没错!你学了它一定不会后悔!!
少废话,正题如下:
首先,概念必须明白:
轻儿子&重儿子
对于一个节点,在它的所有儿子中,size(子树大小)最大的一个就是重儿子,其余的都是轻儿子。重儿子所在的树链叫做重链,其余叫做轻链。
图中共有5条重链,分别是:
1----2----6----3
5
3
4----7
8.
我们需要维护什么?
- tree数组,表示整棵树上的信息
- tree[i].depth表示i的的深度
- tree[i].top表示i所在的重链顶端的结点
- tree[i].father表示i点的父亲
- tree[i].key表示i点的权值
- tree[i].To_tree表示节点i在线段树中的位置
- tree[i].size表示以i为根的子树大小
- tree[i].heavy_son表示i节点的重儿子
- To_num[i]表示线段树中的i号节点对应的是原树中的位置
//鉴于我是个蒟蒻,这里之讲用数据结构线段树维护
怎么维护?
第一次dfs,我们从根开始遍历整棵树,然后记录每个点的【depth,heavy_son,size,father,key】(dfs)。
CODE:
int dfs(int t)
{
int y,mx=0;
tree[t].size=1;
bz[t]=true;
for (int i=last[t];i;i=next[i])
{
y=tov[i];
if(!tree[y].dep)
{
tree[y].depth=tree[t].depth+1;
tree[y].father=t;
tree[y].key=len[i];
dfs(y);
tree[t].size+=tree[y].size;
if(tree[y].size>mx)
{
mx=tree[y].size;
tree[t].heavy_son=y;
}
}
}
}
人工栈CODE:
void biuld_tree()
{
int i,x,y;
tot=1;
d[1].t=root;tree[1].depth=1;
for (i=1;i<=n;++i) cur[i]=last[i];
while(tot)
{
x=d[tot].t;
i=cur[x];
while(tree[tov[i]].depth) i=next[i];
if(!i)
{
d[tot--].maxx=0;
++tree[x].size;
tree[d[tot].t].size+=tree[x].size;
if(tree[x].size>d[tot].maxx) d[tot].maxx=tree[x].size,tree[d[tot].t].hs=x;
continue;
}
cur[x]=next[i];
y=tov[i];
tree[y].fath=x;
tree[y].depth=tree[x].depth+1;
num[y]=number[i];
d[++tot].t=y;
}
}
操作:
- 建树
- 查询
查询包括:
LCA查询
区间答案查询
建树
建树的思想就是dfs,每次先搜索重儿子,保证重链上的每个点在线段树的位置是连续的。维护出每个点的【top,To_tree】以及To_num。
CODE:
void build_dfs(int t,int k)
{
int y;
bz[t]=true;
tree[t].top=k;
tree[t].To_tree=++tot;
To_num[tot]=t;
if(tree[t].heavy_son) dfs2(tree[t].heavy_son,k);
for (int i=last[t];i;i=next[i])
{
y=tov[i];
if(y!=tree[t].father&&y!=tree[t].heavy_son) build_dfs(y,y);
}
}
人工栈CODE:
void complete_tree()
{
int i,x,y;
tot=1;sz=1;
d[1].t=root;d[1].topp=root;
tree[1].To_tree=1;tree[1].top=1;
bz[0]=1;to_num[1]=1;
for (i=1;i<=n;++i) cur[i]=last[i];
while(tot)
{
x=d[tot].t;
tree[x].top=d[tot].topp;
bz[x]=true;
if(!bz[tree[x].heavy_son])
{
tree[tree[x].heavy_son].top=d[tot].topp;
d[++tot].t=tree[x].heavy_son;
d[tot].topp=tree[tree[x].heavy_son].top;
tree[tree[x].heavy_son].To_tree=++sz;
to_num[sz]=tree[x].heavy_son;
continue;
}
i=cur[x];
while(bz[tov[i]]&&i) i=next[i];
if(!i)
{
d[tot--].topp=0;
continue;
}
cur[x]=next[i];
y=tov[i];
tree[tree[x].hs].top=d[tot].topp;
tree[y].To_tree=++sz;
to_num[sz]=y;
d[++tot].t=y;d[tot].topp=y;
}
}
查询
LCA查询
绝对比树上倍增的查询简单!
我们查询x,y的LCA,使用树链剖分。
STEPS:
- 我们查看当前tree[x].top是否与tree[y].top相等
- 如果不相等,那么就将深度较大的(假设为x)跳到当前重链顶端的父亲。
- 一直这样迭代,直到它们top相等为止。
- 如果它们的top相等,有两种情况:
①x=y
②y是x的祖先(或x是y的祖先)
那么LCA就是深度较小的那一个!
是不是很简单~
CODE:
int query_LCA(int x,int y)
{
while(tree[x].top!=tree[y].top)
{
if(tree[tree[x].top].depth>tree[tree[y].top].depth) x=tree[tree[x].top].father;
else y=tree[tree[y].top].father;
}
return tree[x].depth<=tree[y].depth?x:y;
}
区间极值或和查询
合理运用线段树等数据结构,在寻找LCA过程中,顺带处理区间极值或和的查询。
CODE:
int query_ans(int x,int y)
{
int ans=INF;
while(tree[x].top!=tree[y].top)
{
if(tree[tree[x].top].depth>tree[tree[y].top].depth)
{
ans=min(ans,find(1,n,1,tree[tree[x].top].To_tree,tree[x].To_tree));
x=tree[tree[x].top].father;
}
else
{
ans=min(ans,find(1,n,1,tree[tree[y].top].To_tree,tree[y].To_tree));
y=tree[tree[y].top].father;
}
}
if(x==y) return ans;
if(tree[x].depth<tree[y].depth) ans=min(ans,find(1,n,1,tree[tree[x].heavy_son].To_tree,tree[y].To_tree));
else ans=min(ans,find(1,n,1,tree[tree[y].heavy_son].To_tree,tree[x].To_tree));
return ans;
}
本CODE为查询链上最小值
基本知识讲完了,重在理解,你明白了吗?
来道例题:
【NOIP2013提高组day1】货车运输
Time Limits: 1000 ms Memory Limits: 131072 KB Detailed Limits
Description
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
Input
第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意:x 不等于 y。
Output
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。
Sample Input
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
Sample Output
3
-1
3
Data Constraint
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q < 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q < 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q < 30,000,0 ≤ z ≤ 100,000。
给点时间思考~
考虑~
我们讲的是树链剖分,想想怎么才能和它挂钩~
~
有想法吗?
好,讲正解:
很显然,首先我们要做一个最大生成树,用于方便我们求最小值的最大值~这个没有问题吧。
然后就可以两种选择,因为没有权值修改,所以直接用倍增做也是可以的。
当然我们现在讲树链剖分。
如果你刚刚听懂了的话,现在应该直到这棵树应该怎么剖~
CODE:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
struct Moon{int top,To_tree,heavy_son,father,depth,size;long long key;};
struct Candy{int x,y,z;};
int father[10001];
Moon tree[10001];
Candy a[50001];
int n,m,q,tot;
int To_num[1000001];
int next[50001],last[50001],len[50001],tov[50001];
long long f[1000001];
bool bz[10001];
bool cmp(Candy a,Candy b){return a.z>b.z;}
void insert(int x,int y,int z)
{
tov[++tot]=y;
len[tot]=z;
next[tot]=last[x];
last[x]=tot;
}
int getfather(int x)
{
if(father[x]==x) return x;
father[x]=getfather(father[x]);
return father[x];
}
void dfs(int t)
{
int y,mx=0;
tree[t].size=1;
bz[t]=true;
for (int i=last[t];i;i=next[i])
{
y=tov[i];
if(!tree[y].depth)
{
tree[y].depth=tree[t].depth+1;
tree[y].father=t;
tree[y].key=len[i];
dfs(y);
tree[t].size+=tree[y].size;
if(tree[y].size>mx)
{
mx=tree[y].size;
tree[t].heavy_son=y;
}
}
}
}
void dfs2(int t,int k)
{
int y;
bz[t]=true;
tree[t].top=k;
tree[t].To_tree=++tot;
To_num[tot]=t;
if(tree[t].heavy_son)dfs2(tree[t].heavy_son,k);
for (int i=last[t];i;i=next[i])
{
y=tov[i];
if(y!=tree[t].father&&y!=tree[t].heavy_son) dfs2(y,y);
}
}
void buildtree(int l,int r,int v)
{
if(l==r)
{
f[v]=tree[To_num[l]].key;
return;
}
int mid=(l+r)/2;
buildtree(l,mid,v*2);
buildtree(mid+1,r,v*2+1);
f[v]=min(f[v*2],f[v*2+1]);
}
long long find(int l,int r,int v,int x,int y)
{
if(l==x&&r==y) return f[v];
int mid=(l+r)/2;
if(y<=mid) return find(l,mid,v*2,x,y);
else if(x>mid) return find(mid+1,r,v*2+1,x,y);
else return min(find(l,mid,v*2,x,mid),find(mid+1,r,v*2+1,mid+1,y));
}
long long query_ans(int x,int y)
{
long long ans=9187201950435737471;
while(tree[x].top!=tree[y].top)
{
if(tree[tree[x].top].depth>tree[tree[y].top].depth)
{
ans=min(ans,find(1,n,1,tree[tree[x].top].To_tree,tree[x].To_tree));
x=tree[tree[x].top].father;
}
else
{
ans=min(ans,find(1,n,1,tree[tree[y].top].To_tree,tree[y].To_tree));
y=tree[tree[y].top].father;
}
}
if(x==y) return ans;
if(tree[x].depth<tree[y].depth) ans=min(ans,find(1,n,1,tree[tree[x].heavy_son].To_tree,tree[y].To_tree));
else ans=min(ans,find(1,n,1,tree[tree[y].heavy_son].To_tree,tree[x].To_tree));
return ans;
}
int main()
{
freopen("truck.in","r",stdin);
freopen("truck.out","w",stdout);
scanf("%d%d",&n,&m);
int i,j,xx,yy;
for (i=1;i<=n;++i)father[i]=i;
for (i=1;i<=m;++i)scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
sort(a+1,a+1+m,cmp);
for (i=1;i<=m;++i)
{
xx=getfather(a[i].x);
yy=getfather(a[i].y);
if(xx!=yy)
{
father[xx]=yy;
insert(a[i].x,a[i].y,a[i].z);
insert(a[i].y,a[i].x,a[i].z);
}
}
tree[1].depth=1;
tot=0;
for (int i=1;i<=n;i++)
if (!bz[i])
{
tree[i].depth=1;
tree[i].father=0;
dfs(i),dfs2(i,i);
}
memset(f,127,sizeof(f));
buildtree(1,n,1);
scanf("%d",&q);
for (i=1;i<=q;++i)
{
scanf("%d%d",&xx,&yy);
if(getfather(xx)!=getfather(yy)) printf("-1\n");
else printf("%lld\n",query_ans(xx,yy));
}
}
再推荐几道例题:
JZOJsenior2753.【2012东莞市选】树(tree)
【ZJOI2008】树的统计
树的统计CODE:
#include<cstdio>
#include<algorithm>
#define MAXN 30005
#define INF 1<<30
using namespace std;
int l[MAXN],r[MAXN];
struct node{int sum,max;};
node tree[MAXN*4];
struct Edge{int v,next;};//v:value
Edge edge[MAXN*2];
int head[MAXN],now,val[MAXN],link[MAXN],dep[MAXN],fa[MAXN],sonTree[MAXN],heavySon[MAXN],tot,top[MAXN],num[MAXN];
int n,u,v,q;
void addEdge(int u,int v)
{
now++;
edge[now].v=v;
edge[now].next=head[u];
head[u]=now;
}
void buildTree(int now,int l,int r)
{
if (l==r)
{
tree[now].max=val[link[l]];//link(position)
tree[now].sum=val[link[l]];
return;
}
int mid=(l+r)/2;
buildTree(now*2,l,mid);
buildTree(now*2+1,mid+1,r);
tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}
void DFS1(int now,int nowFa,int nowDep)
//HeavySon,Dep,SonTree,Father
{
dep[now]=nowDep;fa[now]=nowFa;sonTree[now]=1;
for (int x=head[now];x!=0;x=edge[x].next)
{
if(edge[x].v==nowFa) continue;//bz
DFS1(edge[x].v,now,nowDep+1);
sonTree[now]+=sonTree[edge[x].v];
if(heavySon[now]==0||sonTree[edge[x].v]>sonTree[heavySon[now]]) heavySon[now]=edge[x].v;
}
}
void DFS2(int now,int nowTop)
//num:The numbering after this point is split
//link:The position of the current node in the segment tree
{
tot++;
top[now]=nowTop; num[now]=tot; link[tot]=now;
if (heavySon[now]==0) return;
DFS2(heavySon[now],nowTop);
for (int x=head[now];x!=0;x=edge[x].next)
if(edge[x].v!=heavySon[now]&&edge[x].v!=fa[now]) DFS2(edge[x].v,edge[x].v);
}
void init()
{
scanf("%d",&n);
for (int i=1;i<=n-1;i++)
{
scanf("%d %d",&u,&v);
addEdge(u,v),addEdge(v,u);
}
for (int i=1;i<=n;i++) scanf("%d",&val[i]);
DFS1(1,0,1); DFS2(1,1);
buildTree(1,1,n);
}
void update1(int now,int l,int r,int loc,int delta)
{
if (l==r)
{
tree[now].sum+=delta;
tree[now].max+=delta;
return;
}
int mid=(l+r)/2;
if (loc<=mid) update1(now*2,l,mid,loc,delta);
else update1(now*2+1,mid+1,r,loc,delta);
tree[now].max=max(tree[now*2].max,tree[now*2+1].max);
tree[now].sum=tree[now*2].sum+tree[now*2+1].sum;
}
int query1(int now,int l,int r,int ql,int qr)
{
int ans=0;
if (ql<=l && r<=qr) return tree[now].sum;
int mid=(l+r)/2;
if (ql<=mid) ans+=query1(now*2,l,mid,ql,qr);
if (qr>mid) ans+=query1(now*2+1,mid+1,r,ql,qr);
return ans;
}
int query2(int now,int l,int r,int ql,int qr)
{
int ans=-INF;
if (ql<=l && r<=qr) return tree[now].max;
int mid=(l+r)/2;
if (ql<=mid) ans=max(ans,query2(now*2,l,mid,ql,qr));
if (qr>mid) ans=max(ans,query2(now*2+1,mid+1,r,ql,qr));
return ans;
}
int getMax(int l,int r)
{
int f1=top[l],f2=top[r],ans=-INF,nowAns;
while (f1!=f2)
{
if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
ans=max(ans,query2(1,1,n,num[f1],num[l]));
l=fa[f1]; f1=top[l];
}
if (dep[l]>dep[r]) nowAns=query2(1,1,n,num[r],num[l]);
else nowAns=query2(1,1,n,num[l],num[r]);
ans=max(ans,nowAns);
return ans;
}
int getSum(int l,int r)
{
int f1=top[l],f2=top[r],ans=0,nowAns;
while (f1!=f2)
{
if (dep[f1]<dep[f2]) swap(f1,f2),swap(l,r);
ans+=query1(1,1,n,num[f1],num[l]);
l=fa[f1]; f1=top[l];
}
if (dep[l]>dep[r]) nowAns=query1(1,1,n,num[r],num[l]);
else nowAns=query1(1,1,n,num[l],num[r]);
ans+=nowAns;
return ans;
}
int main()
{
char s[7];
init();
scanf("%d",&q);
for (int i=1;i<=q;i++)
{
scanf("%s %d %d",s,&u,&v);
if (s[0]=='C') update1(1,1,n,num[u],v-val[u]),val[u]=v;//CHANGE
else if (s[1]=='M') printf("%d\n",getMax(u,v));//QMAX
else printf("%d\n",getSum(u,v));//QSUM
}
return 0;
}
做完这几道题您将会是树链剖分大佬~
再来一道(我的)题:
Solution
CODE:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,p,tot,sz;
int x0,y0,k,kind,x,y;
int F[400100];
int last[100010],tov[400010],next[400010],fa[100010];
struct Tree{int To_tree,depth,top,key,fath,heavy_son,size;}tree[100010];
struct Edge{int x,y,z;}edge[200010];
bool cmp(Edge a,Edge b){return a.z<b.z;}
void insert(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
int getfather(int x)
{
if(fa[x]==x) return x;
fa[x]=getfather(fa[x]);
return fa[x];
}
void dfs_PREPARE(int x)
{
int mx=0;
for (int i=last[x];i;i=next[i])
{
if(!tree[tov[i]].depth)
{
tree[tov[i]].fath=x;
tree[tov[i]].depth=tree[x].depth+1;
tree[tov[i]].key=0;
dfs_PREPARE(tov[i]);
tree[x].size+=tree[tov[i]].size;
if(tree[tov[i]].size>mx)
{
mx=tree[tov[i]].size;
tree[x].heavy_son=tov[i];
}
}
}
tree[x].size++;
}
void dfs_TREE(int x,int Top)
{
tree[x].top=Top;
tree[x].To_tree=++sz;
if(tree[x].heavy_son)dfs_TREE(tree[x].heavy_son,Top);
for (int i=last[x];i;i=next[i])
if(tov[i]!=tree[x].fath&&tov[i]!=tree[x].heavy_son) dfs_TREE(tov[i],tov[i]);
}
void change_Add(int l,int r,int v,int x,int y)
{
if(l==r)
{
F[v]+=y;
return;
}
int mid=(l+r)/2;
if(x<=mid) change_Add(l,mid,v*2,x,y);
else change_Add(mid+1,r,v*2+1,x,y);
F[v]=F[v*2]+F[v*2+1];
}
int Query(int l,int r,int v,int x,int y)
{
if(l==x&&r==y) return F[v];
int mid=(l+r)/2;
if(y<=mid) return Query(l,mid,v*2,x,y);
else if(x>mid) return Query(mid+1,r,v*2+1,x,y);
else return Query(l,mid,v*2,x,mid)+Query(mid+1,r,v*2+1,mid+1,y);
}
int find(int x,int y)
{
int sum=0,k=0;
while (x!=y)
{
if (tree[x].depth<tree[y].depth) swap(x,y);
int u=tree[x].top, v=tree[y].top;
if (u==v)
{
sum+=Query(1,sz,1,tree[y].To_tree,tree[x].To_tree);
return sum;
}
if (tree[u].depth<tree[v].depth) swap(x,y),swap(u,v);
sum+=Query(1,sz,1,tree[u].To_tree,tree[x].To_tree);
x=tree[u].fath;
}
sum+=Query(1,sz,1,tree[x].To_tree,tree[x].To_tree);
return sum;
}
int main()
{
scanf("%d%d",&n,&m);
int i,j,xx,yy;
for (i=1;i<=m;++i)
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
sort(edge+1,edge+1+m,cmp);
for (i=1;i<=n;++i) fa[i]=i;
for (i=1;i<=m;++i)
{
xx=getfather(edge[i].x);
yy=getfather(edge[i].y);
if(xx!=yy)
{
fa[xx]=yy;
insert(edge[i].x,edge[i].y);
insert(edge[i].y,edge[i].x);
++k;
}
if(k==n-1) break;
}
tree[1].depth=1;
dfs_PREPARE(1);
dfs_TREE(1,1);
scanf("%d\n",&p);
while (p--)
{
scanf("%d%d%d",&x0,&y0,&k);
for (i=1;i<=k;++i)
{
scanf("%d%d%d",&kind,&x,&y);
if(kind==2)y=-y;
change_Add(1,sz,1,tree[x].To_tree,y);
tree[x].key+=y;
}
printf("%d\n",find(x0,y0));
}
}