LCA问题【RMQ+Tarjan】

LCA-求树上两点最近公共祖先问题

lrj的紫书上提供了一种将LCA问题转化为RMQ问题的方法,即dfs一次处理出一个序列,first(u)代表u第一次出现的下标,则对于u,v的最近公共祖先的下标即为RMQ(first(u), first(v))。

LCA->RMQ(在线处理):

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N = 40010, M = 25;
 4 int ver[2*N], R[2*N], first[N], dir[N], dp[2*N][M], tot;
 5 /*ver[i]保存下标为i的结点编号,R[i]为下标为i的结点深度,first[i]为编号为i的结点第一次出现的下标,
 6 dp[i][j]为起始下标为i,长度为1<<j的区间的最小深度的结点的下标*/
 7 bool vis[N];
 8 struct Node {
 9     int to, w;
10 };
11 vector<Node> G[N];
12 
13 void dfs(int u, int dep) {
14     vis[u] = true; ver[++tot] = u; first[u] = tot; R[tot] = dep;
15     for (int i = 0; i < G[u].size(); ++i) {
16         int v = G[u][i].to, w = G[u][i].w;
17         dir[v] = dir[u] + w;  
18         if (vis[v]) continue;
19         dfs(v, dep+1);
20         ver[++tot] = u; R[tot] = dep; 
21     }
22 }
23 
24 void RMQ_init(int l, int r) {
25     for (int i = l; i <= r; ++i) dp[i][0] = ver[i];
26     for (int k = 1; (1<<k) <= r-l+1; ++k)
27         for (int i = l; i + (1<<k) <= r; ++i) {
28             int a = dp[i][k-1], b = dp[i+(1<<(k-1))][k-1];
29             if (R[a] > R[b]) dp[i][k] = b;
30             else dp[i][k] = a;
31         }
32 }
33 
34 int RMQ(int l, int r) {
35     int k = 0;
36     while ((1<<(k+1)) <= r-l+1) ++k;
37     int a = dp[l][k], b = dp[r-(1<<k)][k];
38     if (R[a] > R[b]) return b;
39     else return a; 
40 }
41 
42 int LCA(int u, int v) {
43     int x = first[u], y = first[v];
44     if (x > y) swap(x, y);
45     int res = RMQ(x, y);
46     return ver[res];
47 }
48 
49 int main() {
50     int n;
51     while (cin >> n) {
52         memset(dir, 0, sizeof(dir));
53         memset(vis, 0, sizeof(vis));
54         for (int i = 1; i <= n; ++i) G[i].clear();
55         tot = 0;
56         int a, b, c;
57         for (int i = 1; i < n; ++i) {
58             cin >> a >> b >> c;
59             G[a].push_back(Node{b, c});
60         }
61         dfs(1, 1);
62         RMQ_init(1, 2*n-1);
63         int q, u, v;
64         cin >> q;
65         while (q--) {
66             cin >> u >> v;
67             cout << LCA(u, v) << endl;
68         }
69     }
70 
71     return 0;
72 }

Tarjan算法(离线处理):

具体思想不再阐述,下图代表处理编号10的结点的询问时的状态(同色代表处于同一个集合);

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 10005;
 4 int p[maxn], anc[maxn];
 5 bool vis[maxn];
 6 vector<int> G[maxn];
 7 vector<int> q[maxn];
 8 
 9 int find(int x) {return x == p[x] ? x : p[x] = find(p[x]);}
10 
11 void Union(int x, int y) {
12     x = find(x); y = find(y);
13     if (x == y) return;
14     p[y] = x;
15 }
16 
17 void LCA(int u) {
18     anc[u] = u;
19     for (int i = 0; i < G[u].size(); ++i) {
20         int v = G[u][i];
21         LCA(v);
22         Union(u, v);
23         anc[find(u)] = u;
24     }
25     vis[u] = true;
26     for (int i = 0; i < q[u].size(); ++i) {
27         int v = q[u][i];
28         if (!vis[v]) continue;
29         printf("LCA(%d, %d): %d
", u, v, anc[find(v)]);
30     }
31 }
32 
33 int main() {
34     int n;
35     while (~scanf("%d", &n)) {
36         int a, b, k;
37         memset(vis, 0, sizeof(vis));
38         for (int i = 1; i <= n; ++i) G[i].clear(), p[i] = i;
39         for (int i = 1; i < n; ++i) {
40             scanf("%d%d", &a, &b);
41             G[a].push_back(b);
42         }
43         scanf("%d", &k);
44         for (int i = 0; i < k; ++i) q[i].clear();
45         while (k--) {
46             scanf("%d%d", &a, &b);
47             q[a].push_back(b);
48             q[b].push_back(a);
49         }
50         LCA(1);
51     }
52 
53     return 0;
54 }
原文地址:https://www.cnblogs.com/robin1998/p/6591679.html