最近公共祖先
倍增(略)
离线Tarjan
自己写的简略版
1 | int ff[N]; |
2 | int find2(int x){ |
3 | if(ff[x] != x) ff[x] = find2(ff[x]); |
4 | return ff[x]; |
5 | } |
6 | bool vis[N]; |
7 | void tarjan(int x){ |
8 | ff[x] = x; |
9 | vis[x] = 1; |
10 | for(auto v: e[x]){ |
11 | tarjan(v); |
12 | ff[v] = x; |
13 | } |
14 | for(auto v: qry[x]){ |
15 | if(vis[v]){ |
16 | int lca = find2(v); //本次询问x与v的lca |
17 | } |
18 | } |
19 | } |
oiwiki模板
1 |
|
2 |
|
3 |
|
4 | using namespace std; |
5 | |
6 | class Edge { |
7 | public: |
8 | int toVertex, fromVertex; |
9 | int next; |
10 | int LCA; |
11 | Edge() : toVertex(-1), fromVertex(-1), next(-1), LCA(-1){}; |
12 | Edge(int u, int v, int n) : fromVertex(u), toVertex(v), next(n), LCA(-1){}; |
13 | }; |
14 | |
15 | const int MAX = 100; |
16 | int head[MAX], queryHead[MAX]; |
17 | Edge edge[MAX], queryEdge[MAX]; |
18 | int parent[MAX], visited[MAX]; |
19 | int vertexCount, edgeCount, queryCount; |
20 | |
21 | void init() { |
22 | for (int i = 0; i <= vertexCount; i++) { |
23 | parent[i] = i; |
24 | } |
25 | } |
26 | |
27 | int find(int x) { |
28 | if (parent[x] == x) { |
29 | return x; |
30 | } else { |
31 | return find(parent[x]); |
32 | } |
33 | } |
34 | |
35 | void tarjan(int u) { |
36 | parent[u] = u; |
37 | visited[u] = 1; |
38 | |
39 | for (int i = head[u]; i != -1; i = edge[i].next) { |
40 | Edge& e = edge[i]; |
41 | if (!visited[e.toVertex]) { |
42 | tarjan(e.toVertex); |
43 | parent[e.toVertex] = u; |
44 | } |
45 | } |
46 | |
47 | for (int i = queryHead[u]; i != -1; i = queryEdge[i].next) { |
48 | Edge& e = queryEdge[i]; |
49 | if (visited[e.toVertex]) { |
50 | queryEdge[i ^ 1].LCA = e.LCA = find(e.toVertex); |
51 | } |
52 | } |
53 | } |
54 | |
55 | int main() { |
56 | memset(head, 0xff, sizeof(head)); |
57 | memset(queryHead, 0xff, sizeof(queryHead)); |
58 | |
59 | cin >> vertexCount >> edgeCount >> queryCount; |
60 | int count = 0; |
61 | for (int i = 0; i < edgeCount; i++) { |
62 | int start = 0, end = 0; |
63 | cin >> start >> end; |
64 | |
65 | edge[count] = Edge(start, end, head[start]); |
66 | head[start] = count; |
67 | count++; |
68 | |
69 | edge[count] = Edge(end, start, head[end]); |
70 | head[end] = count; |
71 | count++; |
72 | } |
73 | |
74 | count = 0; |
75 | for (int i = 0; i < queryCount; i++) { |
76 | int start = 0, end = 0; |
77 | cin >> start >> end; |
78 | |
79 | queryEdge[count] = Edge(start, end, queryHead[start]); |
80 | queryHead[start] = count; |
81 | count++; |
82 | |
83 | queryEdge[count] = Edge(end, start, queryHead[end]); |
84 | queryHead[end] = count; |
85 | count++; |
86 | } |
87 | |
88 | init(); |
89 | tarjan(1); |
90 | |
91 | for (int i = 0; i < queryCount; i++) { |
92 | Edge& e = queryEdge[i * 2]; |
93 | cout << "(" << e.fromVertex << "," << e.toVertex << ") " << e.LCA << endl; |
94 | } |
95 | |
96 | return 0; |
97 | } |
用欧拉序列转化为 RMQ 问题
对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 $2n - 1$ 的序列,这个序列被称作这棵树的欧拉序列。
在下文中,把结点 $u$ 在欧拉序列中第一次出现位置编号记为 $pos(u)$(也称作节点 $u$ 的欧拉序),把欧拉序列本身记作 $E[1..2n-1]$ 。
有了欧拉序列,LCA 问题可以在线性时间内转化为 RMQ 问题,即 $pos(LCA(u,v)) = min\{pos(k)|k \in E[pos(u)..pos(v)]\}$。
这个等式不难理解:从 $u$ 走到 $v$ 的过程中一定会经过 $LCA(u,v)$,但不会经过 $LCA(u,v)$ 的祖先。因此,从 $u$ 走到 $v$ 的过程中经过的欧拉序最小的结点就是 $LCA(u,v)$。
用 DFS 计算欧拉序列的时间复杂度是 $O(n)$,且欧拉序列的长度也是 $O(n)$,所以 LCA 问题可以在 $O(n)$ 的时间内转化成等规模的 RMQ 问题。
自己的实现
1 | int ff[N]; |
2 | int dfn[N * 2], pos[N * 2], st[20][N * 2], lg[N * 2], dep[N * 2], tot; |
3 | void dfs(int x, int fa, int d){ |
4 | ff[x] = fa; |
5 | pos[x] = ++tot; |
6 | dfn[tot] = x; |
7 | dep[tot] = d; |
8 | for(auto v: e[x]){ |
9 | dfs(v, x, d + 1); |
10 | dfn[++tot] = x; |
11 | dep[tot] = d; |
12 | } |
13 | } |
14 | void st_pre(){ |
15 | for(int i = 1; i <= tot; i++) st[0][i] = i; |
16 | for(int j = 1; j <= lg[tot]; j++){ |
17 | for(int i = 1; i + (1 << j) - 1 <= tot; i++){ |
18 | st[j][i] = dep[st[j - 1][i]] < dep[st[j - 1][i + (1 << (j - 1))]] ? |
19 | st[j - 1][i] : st[j - 1][i + (1 << (j - 1))]; |
20 | } |
21 | } |
22 | } |
23 | int get(int x, int y){ //查询x,y两点的lca |
24 | int l = pos[x], r = pos[y]; |
25 | if(l > r) swap(l, r); |
26 | int k = lg[r - l + 1]; |
27 | int res = dep[st[k][l]] < dep[st[k][r - (1 << k) + 1]] ? st[k][l] : st[k][r - (1 << k) + 1]; |
28 | return dfn[res]; |
29 | } |
oiwiki实现
1 | int dfn[N << 1], dep[N << 1], dfntot = 0; |
2 | void dfs(int t, int depth) { |
3 | dfn[++dfntot] = t; |
4 | pos[t] = dfntot; |
5 | dep[dfntot] = depth; |
6 | for (int i = head[t]; i; i = side[i].next) { |
7 | dfs(side[i].to, t, depth + 1); |
8 | dfn[++dfntot] = t; |
9 | dep[dfntot] = depth; |
10 | } |
11 | } |
12 | void st_preprocess() { |
13 | lg[0] = -1; // 预处理 lg 代替库函数 log2 来优化常数 |
14 | for (int i = 1; i <= (N << 1); ++i) lg[i] = lg[i >> 1] + 1; |
15 | for (int i = 1; i <= (N << 1) - 1; ++i) st[0][i] = dfn[i]; |
16 | for (int i = 1; i <= lg[(N << 1) - 1]; ++i) |
17 | for (int j = 1; j + (1 << i) - 1 <= ((N << 1) - 1); ++j) |
18 | st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i - 1)] |
19 | ? st[i - 1][j] |
20 | : st[i - 1][j + (1 << i - 1)]; |
21 | } |