题意:基本的lca问题
分析:lca的离线算法为tarjan,tarjan算法的流程如下。dfs遍历树,节点a在遍历完成之后退回到a在树中的父亲节点,然后a在并查集中的father指针会指向这个父亲节点。也就是说一个节点,所有以它的被遍历过的直接子节点为根的子树中的点都会在并查集中被合并到这个点,并以它作为代表元。最开始每个点自己一个集合,每次合并操作都是伴随着遍历中的退后行为进行的。这样就产生了一个性质,在遍历过程中,一个被遍历过的节点的集合代表元在哪,取决于由树根到该节点的路径上我退后到了哪个节点。这样一来,所有遍历过的节点与当前节点的lca也恰好就是它们的代表元了。在遍历离开当前点之前,先将所有遍历过的点与当前点的lca在二维数组中记录下来。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; #define maxn 909 int n, root; bool g[maxn][maxn], hasroot[maxn]; int id[maxn], lcs[maxn][maxn]; int sum[maxn]; void input() { int a, b, m; char st[100]; memset(g, 0, sizeof(g)); memset(hasroot, 0, sizeof(hasroot)); for (int i =0; i < n; i++) { scanf("%d", &a); a--; scanf("%[^0-9]", st); scanf("%d", &m); scanf("%[^0-9]", st); for (int i =0; i < m; i++) { scanf("%d", &b); b--; hasroot[b] =true; g[a][b] = g[b][a] =true; } } for (int i =0; i < n; i++) if (!hasroot[i]) { root = i; break; } } int get(int i) { if ((id[i] == i)) return i; return id[i] =get(id[i]); } void unin(int i, int j) { id[get(i)] =get(j); } void dfs(int rt) { int i; id[rt] = rt; for (i =0; i < n; ++i) if (g[rt][i] &&-1== id[i]) { dfs(i); unin(i, rt); } for (i =0; i < n; ++i) if (-1!= id[i]) lcs[rt][i] = lcs[i][rt] =get(i); } void work() { int m; char st[100]; scanf("%d", &m); for (int i =0; i < m; i++) { int a, b; scanf("%[^0-9]", st); scanf("%d", &a); scanf("%[^0-9]", st); scanf("%d", &b); a--; b--; sum[lcs[a][b]]++; } for (int i =0; i < n; i++) if (sum[i]) printf("%d:%d\n", i +1, sum[i]); } int main() { //freopen("t.txt", "r", stdin); char st[100]; while (scanf("%d", &n) != EOF) { input(); memset(id, -1, sizeof(id)); memset(sum, 0, sizeof(sum)); dfs(root); work(); scanf("%[^0-9]", st); } return 0; }