LCA近期公共祖先
该分析转之:http://kmplayer.iteye.com/blog/604518
1,并查集+dfs
对整个树进行深度优先遍历。并在遍历的过程中不断地把一些眼下可能查询到的而且结果同样的节点用并查集合并.
2,分类。使每一个结点都落到某个类中,到时候仅仅要运行集合查询,就能够知道结点的LCA了。
对于一个结点u.类别有:
以u为根的子树、除类一以外的以f(u)为根的子树、除前两类以外的以f(f(u))为根的子树、除前三类以外的以f(f(f(u)))为根的子树……
类一的LCA为u,类二为f(u),类三为f(f(u)),类四为f(f(f(u)))。这种分类看起来好像并不困难。
但关键是查询是二维的。并没有一个确定的u。接下来就是这个算法的巧妙之处了。
利用递归的LCA过程。
当lca(u)运行完成后,以u为根的子树已经所有并为了一个集合。而一个lca的内部实际上做了的事就是对其子结点。依此调用lca.
当v1(第一个子结点)被lca。正在处理v2的时候,以v1为根的子树+u同在一个集合里。f(u)+编号比u小的u的兄弟的子树 同在一个集合里,f(f(u)) + 编号比f(u)小的 f(u)的兄弟 的子树 同在一个集合里……
而这些集合,对于v2的LCA都是不同的。
因此仅仅要查询x在哪一个集合里,就能知道LCA(v2,x)
另一种可能。x不在不论什么集合里。当他是v2的儿子,v3,v4等子树或编号比u大的u的兄弟的子树(等等)时。就会发生这样的情况。即还没有被处理。
还没有处理过的怎么办?把一个查询(x1,x2)往查询列表里加入两次,一次加入到x1的列表里,一次加入到x2的列表里,假设在做x1的时候发现 x2已经被处理了。那就接受这个询问。(两次中必然仅仅有一次询问被接受).
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std; const int MAXN = 100000 + 10;
int degree[MAXN];
bool vst[MAXN];
int ancestor[MAXN];
int f[MAXN];
int rank[MAXN];
vector<int> tree[MAXN];
vector<int> Qes[MAXN]; int N;
void init(){
for(int i = 0;i <= N;++i){
degree[i] = 0;
vst[i] = false;
ancestor[i] = -1;
f[i] = i;
rank[i] = 0;
tree[i].clear();
Qes[i].clear();
}
} int find(int x){
if(x == f[x])
return x;
return f[x] = find(f[x]);
} void setUnion(int u,int v){
int a = find(u),b = find(v);
if(a != b){
if(rank[a] < rank[b]){
f[a] = b;
} else {
f[b] = a;
if(rank[a] == rank[b]) rank[a]++;
}
}
} void LCA(int u){
ancestor[u] = u;
int sz = tree[u].size();
for(int i = 0;i < sz;++i){
LCA(tree[u][i]);
setUnion(u,tree[u][i]);
ancestor[find(u)] = u;
} vst[u] = 1;
sz = Qes[u].size();
for(int i = 0;i < sz;++i){
if(vst[Qes[u][i]]){
printf("%d\n",ancestor[find(Qes[u][i])]);
return;
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--){
int x,y;
scanf("%d",&N);
init();
for(int i = 1;i < N;++i){
scanf("%d%d",&x,&y);
degree[y]++;
tree[x].push_back(y);
} int s,t;
scanf("%d%d",&s,&t);
Qes[s].push_back(t);
Qes[t].push_back(s); for(int i = 1;i <= N;++i){
if(degree[i] == 0){
LCA(i);
break;
}
}
}
return 0;
}