最近公共祖先

Description

给出一棵有N(编号1到N)个节点的有根树,求出指定节点对的最近公共祖先! 
 
对于树中节点x而言,从根节点到达x的这一条路径中经过的所有节点,都称为x的祖先。 
如上图所表示的树中, 根节点为8。8、4、10、16都是12的祖先。对于6和12这对节点而言,从6出发往上朝根走和从12出发往上朝根走的两条路径最早交汇的地点是4号节点,因此4号点是6和12的最近公共祖先。 
同理,11和9的最近公共祖先是8; 10和3的最近公共祖先是10;2和7的最近公共祖先是4......

Input

第一行,一个整数N。表示树中节点总数 
接下来N-1行,每行两个整数x和y,表示x是y的父亲。 
接下来一行,一个整数M,表示询问的总数 
接下来M行,每行两个整数a和b,表示询问a和b的最近公共祖先。

Output

M行,每行一个整数,表示对应询问的答案。

Sample Input

输入样例1:
16 
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
3
16 7
14 9
3 10
输入样例2:

5
2 3
3 4
3 1
1 5
2
3 5
4 5

Sample Output

输出样例1:
4
8
10
输出样例2:
3
3

Hint

2<=N<=10000 
1<=M<=10000


【分析】

        经典的LCA问题,具体算法可以在网上查询。这里给出倍增算法Tarjan算法的两个代码。


【代码】

/*
    倍增算法
	    dep[i]记录i号节点的深度
		fa[i][k]记录i号节点上数2^k个祖先
		
	dep[]和fa[][]都可以用递推式求得 
	    dep[i]=dep[fa[i][0]]+1;  
		    其中fa[i][0]表示i的最近的祖先,即它的父亲节点 
		fa[i][k]=fa[fa[i][k-1]][k-1]; 
*/ 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
int N,M;
int xt[10005],yt[10005],next[10005],last[10005];  //链式前向星存图 
int dep[10005],fa[10005][20];
void _init()
{
	scanf("%d",&N);
	for(int i=1;i<N;i++)
	{
		scanf("%d%d",&xt[i],&yt[i]);
		fa[yt[i]][0]=xt[i];      //记yt[i]的父亲为xt[i] 
		next[i]=last[xt[i]];
		last[xt[i]]=i;
	}
}
void _DFS(int x)      //预处理倍增算法的dep[]和fa[][]数组 
{
	dep[x]=dep[fa[x][0]]+1;
	int k=ceil(log2(dep[x]));
	for(int i=1;i<=k;i++)
	    fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=last[x];i;i=next[i])
	    _DFS(yt[i]);
}
int _LCA(int x,int y)    //在线求解LCA 
{
	int s=ceil(log2(N));
	if(dep[x]<dep[y])
	    swap(x,y);
	int k=dep[x]-dep[y];    //k为x,y的高度差 
	for(int i=0;i<=s;i++)    //将k分解为2进制数,变n次跳跃为log2(n)次 
	    if((k>>i)&1)
	        x=fa[x][i];   //for进行完后,x,y处于同一高度  
	if(x==y) return x;     
	s=ceil(log2(dep[x]));
	for(int i=s;i>=0;i--)    //将x,y移至第一个分叉处 
	    if(fa[x][i]!=fa[y][i])
	    {
	    	x=fa[x][i];
	    	y=fa[y][i];
	    }
	return fa[x][0];   //返回x的父亲节点 
}
void _solve()
{
	for(int i=1;i<=N;i++)  //找到跟节点,从根开始DFS 
		if(!fa[i][0])
		{  
			_DFS(i);
			break;
		}
	scanf("%d",&M);
	int a,b;
	for(int i=1;i<=M;i++)
	{
		scanf("%d%d",&a,&b);
		printf("%d\n",_LCA(a,b));
	}
}
int main()
{
	_init();
	_solve();
	return 0;
}
-------------------分-----------割-------------线-----------------------
/*
    离线的Tarjan算法 
*/ 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
int N,M,x[10005],y[10005],next[10005],last[10005];   //链式前向星存图 
int ans[10005],du[10005],root;
int father[10005];
int qx[20005],qy[20005],qnext[20005],qlast[20005],tot;   //将问题也链式前向星存图 
bool vis[10005];
void _init()
{
	scanf("%d",&N);
	for(int i=1;i<N;i++)
	{
		scanf("%d%d",&x[i],&y[i]);
		next[i]=last[x[i]];
		last[x[i]]=i;
		du[y[i]]++;
	}
	scanf("%d",&M);
	for(int i=1;i<=M;i++)
	{
		tot++;
	    scanf("%d%d",&qx[tot],&qy[tot]);
	    qnext[tot]=qlast[qx[tot]];
	    qlast[qx[tot]]=tot;
	    tot++;
	    qx[tot]=qy[tot-1];qy[tot]=qx[tot-1];
	    qnext[tot]=qlast[qx[tot]];
	    qlast[qx[tot]]=tot;
	}
}
int _getfather(int x)
{
	if(x!=father[x]) father[x]=_getfather(father[x]);
	return father[x];
}
void _Tarjan(int u)
{
	father[u]=u;
	for(int i=last[u];i;i=next[i])
	{
		int v=y[i];
		if(vis[v]==false)
		{
			_Tarjan(v);
			father[v]=u;
		}
	}
	vis[u]=true;
	for(int i=qlast[u];i;i=qnext[i])
	    if(vis[qy[i]])
	    	ans[(i+1)>>1]=_getfather(qy[i]);
}
void _solve()
{
	for(int i=1;i<=N;i++)
	    if(!du[i])
	    {
	    	root=i;
	    	break;
	    }
	_Tarjan(root);
	for(int i=1;i<=M;i++)
	    printf("%d\n",ans[i]);
}
int main()
{
	_init();
	_solve();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值