方法:dfs+并查集
LCA问题一般是用Tarjan算法来解决,其实对于这道题而言的话只需要用dfs+并查集就可以了。用一句话说就是在深度优先遍历树中节点u的时候,当遍历完u的每个孩子后将u和孩子节点进行合并(union)。
合并的时候需要注意的是不能用并查集的rank优化,因为这里在合并的同时仍要保存原树中的结构。比如,如果对于题目中的第二个用例
2
\
3
/ \
4 1
/
5
要是按照以下代码合并:
void unions(int a,int b)
{
int r1=find(a);
int r2=find(b);
if(rank[r1]>rank[r2])
f[r2]=r1;
else
{
f[r1]=r2;
if(rank[r2]==rank[r1])
rank[r2]++;
}
}
最先合并的是3和4,而且根据上面的代码合并后4会是3的父亲,5是1的父亲,当回溯到3合并3和1的时候,1又会是3的父亲。这样就打乱了原来的结构,答案也就错了。所以在unions(a,b)中直接令f[b]=a即可。当然如果按照上面代码合并的话,另外记录父亲节点也是可以的,但会比较麻烦。
#include <stdio.h>
#include <memory.h>
#define N 10001
int tree[N][101];
int visit[N];
int f[N];
int into[N];
int a,b;
int find(int x)
{
while(x!=f[x])
x=f[x];
return x;
}
//为了保持原来树中的结构和并查集中的一致
//不能使用rank数组,直接f[b]=a即可
//因为深度遍历时,若要unions(a,b),a是b的父亲
void unions(int a,int b)
{
f[b]=a;
}
void lca(int u)
{
f[u]=u;
for(int i=1;i<=tree[u][0];i++)
{
lca(tree[u][i]);
unions(u,tree[u][i]);
}
visit[u]=1;
if(u==a&&visit[b])
printf("%d\n",f[find(b)]);
else if(u==b&&visit[a])
printf("%d\n",f[find(a)]);
return;
}
int main()
{
int t,n,u,v;
scanf("%d",&t);
while(t--)
{
memset(tree,0,sizeof(tree));
memset(visit,0,sizeof(visit));
memset(into,0,sizeof(into));
memset(f,0,sizeof(f));
scanf("%d",&n);
for(int i=0;i<n-1;i++)
{
scanf("%d%d",&u,&v);
tree[u][++tree[u][0]]=v;
into[v]++;
}
scanf("%d%d",&a,&b);
for(int i=1;i<=n;i++)
if(!into[i])
{
lca(i);
break;
}
}
return 0;
}