码迷,mamicode.com
首页 > 其他好文 > 详细

树链剖分求LCA

时间:2016-04-18 22:30:29      阅读:307      评论:0      收藏:0      [点我收藏+]

标签:

这里先推荐两道练习的裸题

 

首先是求点

 

【codevs4605】 LCA

就是求两个点的公共祖先,每次询问xor上上一个询问的答案。

 

先是两遍DFS:

dfs1:把dep、siz、son求出来

dfs2:求出top和w

 

siz[v]表示以v为根的子树的节点数

dep[v]表示v的深度(根深度为1)

top[v]表示v所在的链的顶端节点

fa[v]表示v的父亲

son[v]表示与v在同一重链上的v的儿子节点

w[v]结点编号

 

int lca(int x,int y)
{
  while (top[x]!=top[y])
  {
    int a=fa[top[x]];
    int b=fa[top[y]];
    if (dep[a]<dep[b])
      swap(a,b),swap(x,y);
    x=fa[top[x]];
  }
  return dep[x]<dep[y] ? x : y;
}

下面来分析这个算法。

 

1:如果top[a]==top[b],说明a和b在同一条重链上,显然它们中深度较小的点即为它们的最近公共祖先。

2:如果top[a]!=top[b],(说明a,b在不同的重链上)且a的深度较大,则此时a,b的LCA不可能在a所在的重链上。

因为如果a,b的LCA在a所在重链上,那么top[a]显然也为a,b的公共祖先,则若dep[up[a]]]>dep[b],则显然不可能,若dep[up[a]]]<=dep[b],则设dep[up[a]]]为d,因为d>=dep[b],所以我沿着b向上搜索,在深度d处也可以找到一个点C为b的祖先,又因为a,b不在同一条重链上,所以top[a]!=C,这就意味着在同一深度,b有两个不同的祖先,这是不可能的(因为是一棵树),所以,LCA不可能在a所在的重链上。所以我们可以将a上升到up[a]的父节点,这时a,b的LCA没有变化。

3:若果top[a]!=top[b],且b的深度较大,同理我们可将b上升到up[b]的父节点。

4: a,b不停地上升,所以一定可以找到a,b的LCA。

因为我们知道,在树中任意一点到根的路径上,重链数不会超过(logn),所以我们可以在O(logn)的时间复杂度内,将a,b的LCA求出来。

(证明来自:http://www.xuebuyuan.com/552070.html)

 

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

#define N 100010

struct edge
{
int to,next;
}e[N<<1];
int head[N<<1];
int cnt;

int fa[N],dep[N],son[N],top[N],siz[N],pos[N];

int n,m;

int x,y;
int ans;

int root;

void link(int x,int y)
{
  e[++cnt]=(edge){x,head[y]};
  head[y]=cnt;
}

void dfs(int x,int d)
{
  dep[x]=d;
  siz[x]++;
  for (int i=head[x];i;i=e[i].next)
  {
    dfs(e[i].to,d+1);
    siz[x]+=siz[e[i].to];
    if (siz[e[i].to]>siz[son[x]])
      son[x]=e[i].to;
  }
}

void dfs2(int x,int d)
{
  pos[x]++;
  top[x]=d;
  if (son[x])
    dfs2(son[x],d);
  for (int i=head[x];i;i=e[i].next)
    if (e[i].to!=son[x])
      dfs2(e[i].to,e[i].to);
}

int lca(int x,int y)
{
  while (top[x]!=top[y])
  {
    int a=fa[top[x]];
    int b=fa[top[y]];
    if (dep[a]<dep[b])
      swap(a,b),swap(x,y);
    x=fa[top[x]];
  }
  return dep[x]<dep[y] ? x : y;
}

int main()

  scanf("%d",&n);
  for (int i=1;i<=n;i++)
  {
    scanf("%d",&fa[i]);
    if (!fa[i])
    {
      root=i;
      continue;
    }
    link(i,fa[i]);
  }
  dfs(root,1);
  cnt=0;
  top[root]=root;
  dfs2(root,root);
  scanf("%d",&m);
  while (m--)
  {
    scanf("%d%d",&x,&y);
    x^=ans;
    y^=ans;
    ans=lca(x,y);
    printf("%d\n",ans);
  }
  return 0;
}

 

 然后是求值

 

【codevs2370】 小机房的树

求树上两点距离

 

我写了树链剖分

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

#define N 100010

struct edge
{
	int to,val,next;
}e[N<<1];
int head[N<<1];
int cnt;

int fa[N],dep[N],son[N],top[N],siz[N],pos[N];
int v1[N],v2[N];

int n,m;

int u,v,w;
int x,y;

int root;

void link(int x,int y,int z)
{
	e[++cnt]=(edge){y,z,head[x]};
	head[x]=cnt;
}

void dfs(int x,int d)
{
	dep[x]=d;
	siz[x]++;
	for (int i=head[x];i;i=e[i].next)
		if (!dep[e[i].to])
		{
			fa[e[i].to]=x;
			dfs(e[i].to,d+1);
			siz[x]+=siz[e[i].to];
			v1[e[i].to]=e[i].val;
			if (siz[e[i].to]>siz[son[x]])
				son[x]=e[i].to;
		}
}

void dfs2(int x,int d)
{
	top[x]=d;
	if (son[x])
	{
		v2[son[x]]=v2[x]+v1[son[x]];
		dfs2(son[x],top[x]);
	}
	for (int i=head[x];i;i=e[i].next)
		if (e[i].to!=son[x] && e[i].to!=fa[x])
			dfs2(e[i].to,e[i].to);
}

int lca(int x,int y)
{
	int res(0);
	while (top[x]!=top[y])
	{
		int a=fa[top[x]];
		int b=fa[top[y]];
		if (dep[a]<dep[b])
			swap(a,b),swap(x,y);
	   res+=v2[x]+v1[top[x]];
	   x=a;
	}
	if (dep[x]<dep[y])
		swap(x,y);
	return res+v2[x]-v2[y];
}

int main()
{
	scanf("%d",&n);
	for (int i=1;i<n;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		link(++u,++v,w);
		link(v,u,w);
	}
	dfs(1,1);
	dfs2(1,1);
	scanf("%d",&m);
	while (m--)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(++x,++y));
	}
	return 0;
}


复杂度O(qlogn),还是比较快的。

树链剖分求LCA

标签:

原文地址:http://www.cnblogs.com/yangjiyuan/p/5399655.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!