標籤:
給定一棵樹求任意兩個節點的公用祖先
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;}
POJ 1330 Nearest Common Ancestors(LCA模板)