Nearest Common Ancestors(最近公共祖先)
题目描述
有根树是一个在计算机科学和工程学中众所周知的数据结构。如下图所示的例子:
在上图,每个结点用1-16中的一个整数标记。结点8是这棵树的根。
一个结点x是一个结点y的祖先当且仅当结点x是在根和结点y的路径上。例如,结点4是结点16的祖先,结点10也是结点16的祖先。实际上,结点8,4和12都是结点16的祖先。记住每个结点都是它自己的祖先。结点8,4,6和7都是结点7的祖先。
一个结点x被称做结点y和z的公共祖先当且仅当x都是y和z的祖先。因此,结点8 和4是结点16和7的公共祖先。
一个结点x被称做结点y和z的最近公共祖先当且仅当x是y和z的一个公共祖先且在y和z的所有公共祖先中是最近的。所以,结点16和7的最近公共祖先是结点4,因为结点4比结点8更接近结点16和7。
在其它例子中,结点2和3的最近公共祖先结点10,结点6和13的最近公共祖先是结点8,结点4和12的最近公共祖先是结点4。在最后的一个例子中,如果y是z的一个祖先,那么y和z的最近公共祖先是y。
写一个程序找出在一棵树中两个不同结点的最近公共祖先。
输入输出格式
输入格式:
输入包括T组数据。第一行是一个整数T。
对每组数据,第一行是一个整数N,表示这棵树的结点数,其中2<=N<=10,000。
这些结点标记为1,2,…,N。
接下来的N-1行包括一对整数,代表一条边,第一个数是第二个数的父亲。
保证N个结点恰好有N-1条边。
最后的一行包括两个不同的整数,表示要求最近公共祖先的两个不同结点
输出格式:
对每组数据输出一行,包括一个表示所求的两个不同结点的最近公共祖先的整数
输入输出样例
输入样例:
2
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
16 7
5
2 3
3 4
3 1
1 5
3 5
输出样例:
4
3
Code 代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
long long n,t,i,j,x,y,m,r,d[11000],f[11000],p[50],st[11000][30];
vector<long long> e[11000];
void dfs(long long fa)
{
long long i;
for(i=1;i<30;i++)
st[fa][i]=st[st[fa][i-1]][i-1];
for(i=0;i<e[fa].size();i++)
{
long long son=e[fa][i];
st[son][0]=fa;
d[son]=d[fa]+1;
dfs(son);
}
return;
}
long long lca(long long a,long long b)
{
long long l,i;
if(d[a]<d[b])
swap(a,b);
l=d[a]-d[b];
for(i=30;i>=0;i--)
{
if(l>=(1<<i))
{
a=st[a][i];
l-=(1<<i);
}
}
if(a==b)
return a;
for(i=29;i>=0;i--)
{
if(st[a][i]!=st[b][i])
{
a=st[a][i];
b=st[b][i];
}
}
return st[a][0];
}
int main()
{
scanf("%lld",&t);
for(i=1;i<=t;i++)
{
scanf("%lld",&n);
memset(st,0,sizeof(st));
memset(d,0,sizeof(d));
memset(e,0,sizeof(e));
memset(f,0,sizeof(f));
for(j=1;j<n;j++)
{
scanf("%lld%lld",&x,&y);
e[x].push_back(y);
f[y]++;
}
for(j=1;j<=n;j++)
{
if(f[j]==0)
r=j;
}
d[r]=1;
st[r][0]=r;
dfs(r);
scanf("%lld%lld",&x,&y);
m=lca(x,y);
printf("%lld\n",m);
}
return 0;
}