LCA问题的应用很多,例如它可以用来回答这样的询问:“点u和点v的距离是多少?”——由于在树中两点的简单路是唯一的,所以这个距离等于u到LCA(u, v)再到v的距离,关键仍然是LCA。

Tarjan离线算法是基于树的dfs和并查集的,我们令x=LCA(u, v),用这么一句话解释这个算法:如果我们在访问完节点u(及其后代)后,将u的祖先指向为x,那么在访问x的其它后代节点v时,LCA(u,v)就为u的(当前的)祖先点x
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}//x的祖先节点
void tarjan(int u)//在main中调用tarjan(root)
{
vis[u] = true;
int i, v;
for (i = 1; i <= query[u][0]; ++i)//query[u][0]表示形如LCA(u,?)的查询个数
{
v = query[u][i];
if (vis[v]) /*printf LCA(u,v)=find(v) or others*/
}
for (i = 1; i <= son[u][0]; ++i)//son[u][0]表示u的儿子个数
{
v = son[u][i];
if (!vis[v]) tarjan(v), fa[v] = u;
}
}
例题:
POJ 1470 Closest Common Ancestors
此题样例的函数调用过程如下:
tarjan(5) tarjan(1) ++sum[find(5)->5]; //1 done fa[1]=5; tarjan(4) ++sum[find(1)->find(5)->5]; //4 done fa[4]=5; tarjan(2) ++sum[find(4)->find(5)->5]; tarjan(3) ++sum[find(2)->2];//注意此时fa[2]尚未指向5 ++sum[find(1)->find(5)->5]; ++sum[find(4)->find(5)->5]; //3 done fa[3]=2; //2 done fa[2]=5; //5 done
代码实现:
/*375ms,6780KB*/
#include<cstdio>
#include<cstring>
const int mx = 905;
int sum[mx], son[mx][mx], query[mx][mx], fa[mx];
bool vis[mx], hasroot[mx];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void tarjan(int u)
{
vis[u] = true;
int i, v;
for (i = 1; i <= query[u][0]; ++i)
{
v = query[u][i];///要想使sum增加,v必须是先前访问过的点
if (vis[v]) ++sum[find(v)];
}
for (i = 1; i <= son[u][0]; ++i)
{
v = son[u][i];
if (!vis[v]) tarjan(v), fa[v] = u;
}
}
inline void add(int a, int b)
{
++query[a][0];///我们用query[节点a][0]表示查询LCA(a,?)的个数
query[a][query[a][0]] = b;///记录这一查询LCA(a,b)
}
int main()
{
int n, i, j, k, Q;
while (~scanf("%d", &n))
{
memset(vis, 0, sizeof(vis));
memset(sum, 0, sizeof(sum));
memset(hasroot, 0, sizeof(hasroot));
for (i = 1; i <= n; ++i)
{
fa[i] = i, query[i][0] = 0;
scanf("%d", &j);
while (getchar() != ‘(‘);
scanf("%d", &son[j][0]);///注意,我们用son[节点编号][0]表示儿子个数
while (getchar() != ‘)‘);
for (k = 1; k <= son[j][0]; ++k)
{
scanf("%d", &son[j][k]);///记录此节点的儿子节点编号
hasroot[son[j][k]] = true;
}
}
scanf("%d", &Q);
while (Q--)
{
while (getchar() != ‘(‘);
scanf("%d%d", &i, &j);
while (getchar() != ‘)‘);
add(i, j), add(j, i);///双向添加查询
}
for (i = 1; i <= n; ++i)
if (!hasroot[i]) {tarjan(i); break;}///从根节点进入
for (i = 1; i <= n; ++i)
if (sum[i]) printf("%d:%d\n", i, sum[i]);
}
return 0;
}
原文:http://blog.csdn.net/synapse7/article/details/18981701