tarjan算法是离线算法,它必须先将所有的要查询的点对存起来,然后在搜的时候输出结果。
tarjan算法很经典,因为算法的思想很巧妙,利用了并查集思想,在dfs下,将查询一步一步的搜出来。
伪代码如下:
可以看到,对于我们已经保存好的查询,假设为(u,v),u为此时已经搜完的子树的根节点,v的位置就只有两种可能,一种是在u的子树内,另一种就是在其之外。
对于在u的子树内的话,最近公共祖先肯定是可以直接得出为u;
对于在u的子树之外的v,我们已经将v搜过了,且已经知道了v的祖先,那么我们可以根据dfs的思想,v肯定是和u在一颗子树下的,而且这颗子树是使得他们能在一颗子树下面深度最深的。而这个子树的根节点刚好就是v的并查集所保存的祖先。所以对于这种情况的(u,v),它们的最近公共祖先就是v的并查集祖先。
poj 1470
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
#define MAXN 1001
int fa[MAXN];
int ques[MAXN][MAXN];
int ind[MAXN];
bool vis[MAXN];
int cnt[MAXN];
vector<int>edge[MAXN];
int n,m;
int find_father(int k) {
if(fa[k] == k)return k;
else return fa[k] = find_father(fa[k]);
}
void tarjan(int x){
for (int i=1; i<=n; i++) {
if (vis[i] && ques[x][i])
cnt[find_father(i)] += ques[x][i];
}
fa[x] = x;
vis[x] = true;
for (int i=0; i<edge[x].size(); i++) {
tarjan(edge[x][i]);
fa[edge[x][i]] = x;
}
}
int main() {
while (~scanf("%d", &n)) {
for (int i=1; i<=n; i++) {
edge[i].clear();
}
memset(ques, 0, sizeof(ques));
memset(vis, false, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
memset(ind, 0, sizeof(ind));
int a, b;
for (int i=0; i<n; i++) {
scanf("%d:(%d)", &a, &m);
for (int j=0; j<m; j++) {
scanf(" %d", &b);
edge[a].push_back(b);
ind[b]++;
}
}
scanf("%d", &m);
for (int i=0; i<m; i++) {
scanf(" (%d %d)", &a, &b);
ques[a][b]++;
ques[b][a]++;
}
for (int i=1; i<=n; i++)
if (!ind[i]) {
tarjan(i); break;
}
for (int i=1; i<=n; i++)
if (cnt[i]) printf("%d:%d\n", i, cnt[i]);
}
return 0;
}