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;
}