一叶浮萍归大海

人生何处不相逢

浅谈树链剖分

@

什么是树链剖分?

指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、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

  1. 我们查看当前tree[x].top是否与tree[y].top相等
  2. 如果不相等,那么就将深度较大的(假设为x)跳到当前重链顶端的父亲。
  3. 一直这样迭代,直到它们top相等为止。
  4. 如果它们的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));
	}
}
posted @ 2019-08-10 20:07  Chandery  阅读(178)  评论(0编辑  收藏  举报