给定一棵树求任意两个节点的公共祖先
tarjan离线求LCA思想是,先把所有的查询保存起来,然后dfs一遍树的时候在判断。如果当前节点是要求的两个节点当中的一个,那么再判断另外一个是否已经访问过,如果访问过的话,那么它的最近公共祖先就是当前节点祖先。
下面是tarjan离线模板:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 10010; struct Edge { int to, next; }edge[maxn * 2]; //查询 struct Query { int q, next; int index; }query[maxn * 2]; int tot, head[maxn]; //查询的前向星 int cnt, h[maxn]; //查询的答案保存在ans中 int ans[maxn * 2]; int fa[maxn];//并查集 int r[maxn];//并查集集合个数 int ancestor[maxn];//祖先 bool vis[maxn];//访问标记 int Q;//查询总数 void init(int n) { tot = 0; cnt = 0; Q = 0; memset(h, -1, sizeof(h)); memset(head, -1, sizeof(head)); memset(fa, -1, sizeof(fa)); memset(ancestor, 0, sizeof(ancestor)); memset(vis, false, sizeof(vis)); for (int i = 1; i <= n; i++) r[i] = 1; } void addedge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; } void addquery(int u, int v, int index) { query[cnt].q = v; query[cnt].index = index; query[cnt].next = h[u]; h[u] = cnt++; } int find(int x) { if (fa[x] == -1) return x; return fa[x] = find(fa[x]); } void Union(int x, int y) { int t1 = find(x); int t2 = find(y); if (t1 != t2) { if (t1 < t2) { fa[t1] = t2; r[t2] += r[t1]; } else { fa[t2] = t1; r[t1] += r[t2]; } } } void LCA(int u)//tarjan离线算法 { vis[u] = true; ancestor[u] = u; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; if (vis[v]) continue; LCA(v); Union(u, v); ancestor[find(u)] = u; } for (int i = h[u]; i != -1; i = query[i].next) { int v = query[i].q; if (vis[v]) { ans[query[i].index] = ancestor[find(v)]; } } } bool in[maxn]; int main() { int T, n; scanf("%d", &T); while (T--) { scanf("%d", &n); init(n); memset(in, false, sizeof(in)); int u, v; for (int i = 1; i < n; i++) { scanf("%d %d", &u, &v); in[v] = true; addedge(u, v); addedge(v, u); } scanf("%d %d", &u, &v); addquery(u, v, Q);//添加查询 addquery(v, u, Q++); int root; for (int i = 1; i <= n; i++) { if (!in[i]) { root = i; break; } } LCA(root); for (int i = 0; i < Q; i++)//按照顺序打印出来答案 printf("%d\n", ans[i]); } return 0; }
RMQ&LCA在线模板:
RMQ st算法是用来求一段连续的区间最值问题的,如果将树看成一个线性结构,那么它可以快速求出一段区间的最值,那么就可以利用它求出LCA,首先求出一个树的欧拉序列(就是dfs序),然后每个节点都有深度,都有到根节点的距离。保存一个第一次访问到某个节点的编号。这样求两个点的LCA就是求从欧拉序列当中的一段到另外一段(连续的)深度的最小值。直接RMQ就可以了。模板如下:
#include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 20010; int tot, head[maxn]; struct Edge { int to, next; }edge[maxn]; int occur[maxn]; int first[maxn]; int dep[maxn]; bool vis[maxn]; int m; void init() { tot = 0; memset(head, -1, sizeof(head)); memset(vis, false, sizeof(vis)); memset(first, 0, sizeof(first)); m = 0; } void addedge(int u, int v) { edge[tot].to = v; edge[tot].next = head[u]; head[u] = tot++; } void dfs(int u, int depth) { occur[++m] = u; dep[m] = depth; if (!first[u]) first[u] = m; for (int i = head[u]; i != -1; i = edge[i].next) { int v = edge[i].to; dfs(v, depth + 1); occur[++m] = u; dep[m] = depth; } } int Rmin[maxn * 2][32]; void RMQ(int n) { for (int i = 1; i <= n; i++) Rmin[i][0] = i; int k = (int)log2(n); for (int j = 1; j <= k; j++) { for (int i = 1; i + (1 << j) - 1 <= n; i++) Rmin[i][j] = dep[Rmin[i][j - 1]] < dep[Rmin[i + (1 << (j - 1))][j - 1]] ? Rmin[i][j - 1] : Rmin[i + (1 << (j - 1))][j - 1]; } } int query(int a, int b) { int l = first[a], r = first[b]; if (l > r) swap(l, r); int k = (int)log2(r - l + 1); int tmp = dep[Rmin[l][k]] < dep[Rmin[r - (1 << k) + 1][k]] ? Rmin[l][k] : Rmin[r - (1 << k) + 1][k]; return occur[tmp]; } int main() { int T, n; scanf("%d", &T); while (T--) { init(); scanf("%d", &n); int a, b; for (int i = 1; i < n; i++) { scanf("%d %d", &a, &b); addedge(a, b); vis[b] = true; } int root; for (int i = 1; i <= n; i++) { if (!vis[i]) { root = i; break; } } dfs(root, 1); scanf("%d %d", &a, &b); RMQ(m); printf("%d\n", query(a, b)); } return 0; }