题目链接:
[POJ 1470]Closest Common Ancestors[离线LCA]
题意分析:
给出多个查询,查询点u和v的最近公共祖先。输出每个点作为最近公共祖先在查询中出现的次数,0次的不输出。
解题思路:
离线LCA,需要用到tarjan。
和普通的tarjan差不多。多了两样东西:
1.u - > v回溯的时候,更新v的祖先为u
2.在递归结尾,检查查询。
个人感受:
很微妙的东西,需要看着代码感受。
主要原理就是子树被遍历过后,子树内的查询都会结束,那么可以断定(因为内部查询都被处理了),这颗子树内的所有子节点和外界的最近祖先,都是这棵子树的根。
具体代码如下:
#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<string>
#define ll long long
#define pr(x) cout << #x << " = " << (x) << '\n';
using namespace std;
const int INF = 0x7f7f7f7f;
const int MAXN = 1e3 + 111;
const int MAXM = 8e5;
struct Query {
int to, next, id;
}query[2 * MAXM];
int head[MAXN], tol = 0;
vector<int> G[MAXN];
int cnt[MAXN], ancestor[MAXN];
int ans[MAXM];
bool isLeaf[MAXN], vis[MAXN];
int n;
int find(int x) {
return ancestor[x] == x ? x : ancestor[x] = find(ancestor[x]);
}
void init() {
tol = 0;
for (int i = 0; i <= n; ++i) {
isLeaf[i] = cnt[i] = vis[i] = 0;
head[i] = -1;
G[i].clear();
}
}
void addedge(int u, int v, int indx) {
query[tol].to = v;
query[tol].next = head[u];
query[tol].id = indx;
head[u] = tol++;
}
void lca(int u) {
ancestor[u] = u;
vis[u] = 1;
for (int i = 0; i < G[u].size(); ++i) {
int v = G[u][i];
if (vis[v]) continue;
lca(v);
ancestor[v] = u;
}
for (int i = head[u]; ~i; i = query[i].next) {
int v = query[i].to;
if (vis[v]) {
ans[query[i].id] = ancestor[find(v)];
}
}
}
int main()
{
while (~scanf("%d", &n)) {
init();
int u, v, x;
for (int i = 0; i < n; ++i) {
scanf("%d:(%d)", &u, &x);
for (int j = 0; j < x; ++j) {
scanf("%d", &v);
isLeaf[v] = 1;
G[u].push_back(v);
}
}
scanf("%d", &x);
for (int i = 0; i < x; ++i) {
scanf(" (%d %d)", &u, &v);
addedge(u, v, i);
addedge(v, u, i);
}
for (int i = 1; i <= n; ++i) {
if (!isLeaf[i]) {
lca(i);
break;
}
}
for (int i = 0; i < x; ++i) {
++cnt[ans[i]];
}
for (int i = 1; i <= n; ++i) {
if (cnt[i] > 0) {
printf("%d:%d\n", i, cnt[i]);
}
}
}
return 0;
}