前言
本人第一次做LCA,因为这是一个模板题,所以只讲算法思想
链接
http://poj.org/problem?id=1330
大意
在一棵树上,有 n n 个节点,条边,给定两个节点 p,q p , q ,求它们的最近公共祖先
思路
Tarjian
T
a
r
j
i
a
n
算法(注意不是求强联通分量的那个
Tarjian
T
a
r
j
i
a
n
,而是这个人很厉害,发明了许多算法,所以就用他的名字命名)
核心思想:并查集+
dfs
d
f
s
dfs
d
f
s
的过程中,用并查集保存祖先,当发现两个节点都被找到时,就输出先找到的那个节点的祖先。
原理是因为
dfs
d
f
s
是深度优先,所以它先找到的一定深度更浅
代码
#include<cstdio>
#include<cstring>
#define N 10005
#define zero(a) memset(a,0,sizeof(a))//清0函数
using namespace std;bool ok;
int l[N],n,t,tot,p,q,root,f[N];
bool vis[N];
struct node{int from,next,to;}edge[N];//邻接表
void add(int u,int v){edge[++tot].from=u;edge[tot].to=v;edge[tot].next=l[u];l[u]=tot;}//建边
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}//并查集查询
void LRZ()//输入
{
zero(vis);zero(l);//初始化
scanf("%d",&n);int x,y;tot=0;//初始化,记住一个都不能少,要不然会CE或者MLE
for(int i=1;i<n;i++) {scanf("%d%d",&x,&y);add(x,y);vis[y]=true;}//因为这是一个有向图,所以我们要找到一个入度为0的节点作为根节点
scanf("%d%d",&p,&q);//输入
for(int i=1;i<=n;i++)
if(!vis[i]) //入队为0
{root=i;return;}//它就是根节点,赋值,结束
}
void dfs(int k)//dfs
{
if(ok) return;//已经找到直接退出
vis[k]=true;//标记已经走过
if(k==p&&vis[q]) {printf("%d\n",find(q));ok=true;return;}//找到两个就输出先找到的那个的祖宗
if(k==q&&vis[p]) {printf("%d\n",find(p));ok=true;return;}//记得标记
for(int i=l[k];i;i=edge[i].next)//邻接表
{
dfs(edge[i].to);//递归
if(ok) return;//若找到了直接退出
f[find(edge[i].to)]=edge[i].from;//赋值
}
return;
}
void tarjan()//tarjan算法
{
for(int i=1;i<=n;i++) f[i]=i;//并查集初始化
zero(vis);ok=false;//初始化
dfs(root);//搜
return;//返回
}
int main()
{
scanf("%d",&t);//输入
while(t--)
{
LRZ();//输入
tarjan();//计算
}
}