树链剖分简单入门

树链剖分,可以算是一种思想吧,因为它一般会和其它数据结构套在一起用。


之前自己在理解的时候写了个ppt,以便以后能够再忘了的时候看看。


•树链剖分入门
                        ---------By nikelong

•遇到这样一道题:给定一棵树,每条边又一个权值,询问任意两点u,v之间路径上的权值和和极值。
•怎么做?
•LCA?
------LCA可以做,但是如果操作数极大怎么办?



•引入树链剖分
•树链:从根到叶子节点的一条路径就是一条树链
•树链剖分,“望文生义”可以知道是把树链进行剖分。
•树链,我们把它分成两类,一类是重链(不是很重,而是重边组成的链);另一类是轻链(由轻边组成)。
•分成这两类链后会有一些神奇的操作


补充一些概念:
       重儿子:size[u]为v的子节点中size值最大的,                           那么u就是v的重儿子。
     轻儿子:v的其它子节点。
     重边:点v与其重儿子的连边。
     轻边:点v与其轻儿子的连边。
     重链:由重边连成的路径。
     轻链:轻边。
        剖分后的树有如下性质:
     性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
     性质2:从根到某一点的路径上轻链、重链的个数 都不大于logn。


•下面讲一下树链剖分的实质。
•其实质就是把一棵树分成两类边,通过有序的dfs把重链的点编号处理成连接在一起的,之后用其他的数据结构维护重链所在区间(轻链一般是单一的)。
•由定理2可以知道,这样做的时间复杂度大致是log n的。
   本身并不是一种固定的数据结构(个人认为),主要和其他数据结构一起来优化算法。


•如何实现?
我们需要弄清楚我们需要些什么。
Size[u]记录以u为根节点的子树的大小:用在求重儿子
Son[u]记录u的重儿子
Fa[u]记录u的父亲
Top[u]是u所在的重链的顶端
Deep[u]是u在树的深度
W[u]是u与父亲连边在线段树中的序号
有时我们还需要求他东西


•算法实现:
•第一次dfs求出dep,fa,son,size等信息
•第二次dfs求出top(轻重链),w(边的序号)
•之后线段树维护
•线段树是一个很实用的东西,忘掉了快去复习


Void dfs1(int u,int f,int d)//当前u,u->f,u->dep
{
Fa[u]=f;
Dep[u]=d+1;
Size[u]=1;//默认为1,后面累加
Son[u]=0;//附一个初值,0表示没有
    For(int i=head[u];~i;i=e[i].next)//遍历所有边
    { int v=e[i].v;
      if(v==f)continue;//双向建边
      dfs(v,u,d+1);//先处理下一层再回溯
      size[u]+=size[v];//累加size
      if(size[v]>size[son[u]])son[u]=v;//更新重儿子 
    }
}
//这一部分我们求出了很多东西,在第二次dfs的时候可以用了


Void dfs2(int u,int tp)//当前u,u的顶端是tp
{
   //为了使重链的区间连续,我们先处理重链
  w[u]=++len;//编号
  top[u]=tp;//链顶端
  if(son[u])dfs2(son[u],tp);//先处理重儿子
  for(int i=head[u];~i;i=e[i].next)
  {
    int v=e[i].v;
    if(v==fa[u]||v==son[u])continue;//处理轻儿子
    dfs2(v,v);
  }
}
//有了这个映射,就可以套用线段树了

•以下以spoj 375为例
•询问任意两点之间的路径上最大值,可以修改某条边
•N=10000


•定义好各种数组先跑两次dfs预处理一下。
•线段树直接上模板,这道题是单点修改、区间查询。
•这里我们需要考虑这样一件事:对于dfs中访问的边我们都打上了标号,我们是从上往下访问的,那么每条边的信息我们是存在这条边上深度较大的那一个点上,所以对于每个记录下来的整套读入边,我们需要统一哪个深度大,以便查询的时候调用它(深度大所连边)的编号.

•重点!!!!
•树链剖分套用的重点在于查询。
•给定这样的数,红色表示重链
•询问两个矩形之间路径上最小值
•令左边的u,右边v
•得到f1=top[u],f2=top[v]
•很明显dep[f1]>dep[f2]
•我们先处理u和f1,计算[f1,u]上的信息,然后
•u=fa[f1],f1=top[u]
•第二次,f1、f2重合了,u=f1=f2
•然后更新一下[u,v]这一段就好了



•实现:
•void find(int u,int v)
•{
•  int f1=top[u],f2=top[v];
•  while(f1!=f2)
•  {
•   if(dep[f1]<dep[f2]){swap(u,v);swap(f1,f2);}
•   ans=max(ans,query(1,w[f1],w[u]));
•   u=fa[f1];f1=top[u];
•  }//没在一条链上
•  if(u==v)return ans;//使u在上面,处理u-v(son[u]算一条边)
•  if(dep[u]>dep[v])swap(u,v);
•  ans=max(ans,query(1,w[son[u]],w[v]));
•Printf(“%d\n”,ans);
•}
实现:
void find(int u,int v)
{
  int f1=top[u],f2=top[v];
  while(f1!=f2)
  {
   if(dep[f1]<dep[f2]){swap(u,v);swap(f1,f2);}
   ans=max(ans,query(1,w[f1],w[u]));
   u=fa[f1];f1=top[u];
  }//没在一条链上
  if(u==v)return ans;//使u在上面,处理u-v(son[u]算一条边)
  if(dep[u]>dep[v])swap(u,v);
  ans=max(ans,query(1,w[son[u]],w[v]));
Printf(“%d\n”,ans);
}

•大概就这么多了,记得存的边的变化以方便查询。
•Thanks~


另附spoj 375 AC代码(仅供参考,因为写的比较水,代码有点长):

#include<cstdio>
#include<queue>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#define maxn 10000+20
using namespace std;
int n;
int size[maxn];//以u这个点为根的子树总大小
int deep[maxn];//u的深度
int top[maxn];//u这个点的所在链的顶端
int fa[maxn];//u的父亲
int w[maxn];//与其父亲的连边在线段树中的位置 
int son[maxn];//u的重儿子 
int len;//区间长度 
struct node
{
	int u,v,w,next; 
}e[4*maxn];
int d[maxn][3]; 
int head[maxn];
int val[maxn];
int k=1;
void add(int u,int v,int w)
{
	e[k].v=v;
	e[k].w=w;
	e[k].next=head[u];
	head[u]=k++;
}
void pre_dfs(int u,int pre,int dep)
{
	fa[u]=pre;
	deep[u]=dep;
	size[u]=1;
	son[u]=0;
	for(int i=head[u];i!=-1;i=e[i].next)
	{
		if(e[i].v!=pre)
		{
			pre_dfs(e[i].v,u,dep+1);
			size[u]+=size[e[i].v];
			if(size[e[i].v]>size[son[u]])son[u]=e[i].v;
		}
	}
}//这一部分我们处理出了fa[u],size[u],dep[u],son[u].
//第二个dfs我们需要求出每条边在线段树中的位置 
void dfs(int u,int tp)//u,u->top
{
	w[u]=++len;
	top[u]=tp;
	if(son[u]!=0)dfs(son[u],tp);
	for(int i=head[u];i!=-1;i=e[i].next)
	{
		if(e[i].v!=fa[u]&&e[i].v!=son[u])
		{
			dfs(e[i].v,e[i].v);//轻链 
		}
	}
}
struct Node
{
	int Max,l,r;
}T[4*maxn];
void pushup(int x)
{
	T[x].Max=max(T[x*2].Max,T[x*2+1].Max);
}
void build(int u,int l,int r)
{
	T[u].l=l;
	T[u].r=r;
	if(l==r)
	{
		T[u].Max=val[l];
		return ;
	}
	int mid=(l+r)/2;
	build(u*2,l,mid);
	build(u*2+1,mid+1,r);
	pushup(u);
}
void updata(int u,int pos,int v)
{
	if(T[u].l==T[u].r)
	{
		T[u].Max=v;
		return ;
	}
	int mid=(T[u].l+T[u].r)/2;
	if(pos<=mid)updata(u*2,pos,v);
	else updata(u*2+1,pos,v);
	pushup(u);
}
int query(int u,int l,int r)
{
	//查询l,r最大值
	if(T[u].l>=l&&T[u].r<=r)return T[u].Max;
	int mid=(T[u].l+T[u].r)/2;
	int ans=0;
	if(l<=mid)ans=max(ans,query(u*2,l,r));
	if(r>mid)ans=max(ans,query(u*2+1,l,r));
	return ans; 
}
int find(int u,int v)//查询l,r的最大值 
{
	int f1=top[u],f2=top[v];
	int ans=0;
	while(f1!=f2)
	{
		if(deep[f1]<deep[f2])
		{
			swap(f1,f2);
			swap(u,v);
		}
		ans=max(ans,query(1,w[f1],w[u]));//
		u=fa[f1];
		f1=top[u];
	}
	if(u==v)return ans;
	if(deep[u]>deep[v])
	{
		swap(u,v);
	}
	return max(ans,query(1,w[son[u]],w[v]));
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		k=1;
		memset(head,-1,sizeof(head));
		scanf("%d",&n);
		len=0;
		for(int i=1;i<n;i++)
		{
			scanf("%d%d%d",&d[i][0],&d[i][1],&d[i][2]);
			add(d[i][0],d[i][1],d[i][2]);
			add(d[i][1],d[i][0],d[i][2]);
		}
		pre_dfs(1,0,0);//u,u->f,u->dep
		dfs(1,1);
		for(int i=1;i<n;i++)
		{
			if(deep[d[i][0]]>deep[d[i][1]])swap(d[i][0],d[i][1]);;
			val[w[d[i][1]]]=d[i][2];
		}
		build(1,1,len);
		char s[100];
		while(scanf("%s",s)!=EOF)
		{
			if(s[0]=='D')break;
			int l,r;
			scanf("%d%d",&l,&r);
			if(s[0]=='Q')
			{
				printf("%d\n",find(l,r));
			}
			else
			{
				updata(1,w[d[l][1]],r);
			}
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值