http://blog.csdn.net/hnust_xiehonghao/article/details/9109295
对于有根树T的两个节点u、v,最近公共祖先LCA(u,v)表示一个节点x,满足x是u,v的祖先,且x的深度尽可能大
如果把树看成图,就是求到u,v的最短距离
时间复杂度为O(n+q),n为树的节点数,q为询问次数
离线算法
Trajan算法基于深度优先搜索,对于新搜索到的一个节点,首先创建由这个节点构成的集合,再对当前节点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前节点的集合合并,并将当前节点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前节点的所有子树搜索完。这时把当前节点也设为已被检查过的,同时可以处理有关当前节点的LCA询问。
代码流程:
算法:Tarjan_Dfs(u),对u为根的子树进行DFS
输入:树u和LCA查询集合Q
输出:Q的答案
(1) 创建并查集合u
(2) 对于(u,v)属于Q,如果v已经被标记,则Ancestor(u,v) <-v为v所在集合的根
(3) 对于u的每一个儿子v
1.递归调用Tarjan_Dfs(v)
2.合并u和v所在的集合,设根为u
(4)标记u
const int maxn=10000;//节点数
using namespace std;
int f[maxn],size[maxn],in[maxn],vis[maxn],ance[maxn];//集合,名次,入度,标记,祖先
vector<int> node[maxn],que[maxn];//边表,询问
void Init(){
for(int i=0;i<maxn;++i){
node[i].clear();
que[i].clear();
size[i]=1;
f[i]=i;
}
}
int find(int x){
return f[x]==x? x:f[x]=find(f[x]);
}
void Union(int a,int b){//按名次合并
int x=find(a);
int y=find(b);
if(size[x]<=size[y]){
f[x]=y;
size[y]+=size[x];
}else{
f[y]=x;
size[x]+=size[y];
}
}
void LCA(int rt){//dfs
int sz=node[rt].size();
ance[rt]=rt;//创建集合
for(int i=0;i<sz;++i){
LCA(node[rt][i]);//递归调用子树
Union(rt,node[rt][i]);//将子树与父节点合并
ance[find(node[rt][i])]=rt;//将子树所在的集合的祖先指向根
}
vis[rt]=1;
sz=que[rt].size();
for(int i=0;i<sz;++i){
if(vis[que[rt][i]]){
printf("%d\n",ance[find(que[rt][i])]);//v所在集合的根
}
}
}
int main()
{
for(int i=0;i<n;++i){
if(in[i]==0){LCA(i);break;}//找到根节点
}
return 0;
}