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