Blog of RuSun

\begin {array}{c} \mathfrak {One Problem Is Difficult} \\\\ \mathfrak {Because You Don't Know} \\\\ \mathfrak {Why It Is Diffucult} \end {array}

LCA模板

求LCA。

目录

Algorithm I 树上倍增

思想:按照 $2$ 的次幂的层数跳。

复杂度:$O(n \log n + T \log n)$ 。常数较大,洛谷模板 $5.67s$ 。以下是经过预处理 $log$ 优化常数的版本。

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <cstdio>
#include <queue>
#include <climits>
#define INF INT_MAX
using namespace std;
const int N = 5e5 + 10, M = 1e6 + 10;
int n, q, st, d[N], lg[N], fa[N][20];
int idx = -1, hd[N], nxt[M], edg[M];
void bfs()
{
for (int i = 1; i <= n; i++)
d[i] = INF;
d[st] = 1;
queue<int> q;
q.push(st);
while (!q.empty())
{
int t = q.front();
q.pop();
for (int i = hd[t]; ~i; i = nxt[i])
if (d[t] + 1 < d[edg[i]])
{
d[edg[i]] = d[t] + 1;
q.push(edg[i]);
fa[edg[i]][0] = t;
for (int k = 1; (1 << k) <= d[edg[i]]; k++)
fa[edg[i]][k] = fa[fa[edg[i]][k - 1]][k - 1];
}
}
}
int lca(int x, int y)
{
if (d[x] < d[y])
swap(x, y);
while (d[x] > d[y])
x = fa[x][lg[d[x] - d[y]]];
if (x == y)
return x;
for (int k = lg[d[x]]; k >= 0; k--)
if (fa[x][k] != fa[y][k])
{
x = fa[x][k];
y = fa[y][k];
}
return fa[x][0];
}
void add(int x, int y)
{
nxt[++idx] = hd[x];
hd[x] = idx;
edg[idx] = y;
}
int main()
{
cin >> n >> q >> st;
for (int i = 2; i <= n; i++)
lg[i] = lg[i >> 1] + 1;
for (int i = 1; i <= n; i++)
hd[i] = -1;
for (int i = 1, a, b; i < n; i++)
{
cin >> a >> b;
add(a, b);
add(b, a);
}
bfs();
for (int i = 1, a, b; i <= q; i++)
{
cin >> a >> b;
cout << lca(a, b) << endl;
}
return 0;
}

Algorithm II 树链剖分

思想:将树剖成一条一条的链,一条链一条链地向上跳。

复杂度:$O(n + T \log n)$ 。常数极小,可拓展性极强,强烈推荐,洛谷模板 $1.57s$ 。

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10, M = 1e6 + 10;
int n, m, rt, d[N], p[N];
int idx, hd[N], nxt[M], edg[M];
int stmp, sz[N], son[N], top[N];
void dfs1(int x)
{
sz[x] = 1;
son[x] = -1;
for (int i = hd[x]; ~i; i = nxt[i])
if (!d[edg[i]])
{
d[edg[i]] = d[x] + 1;
p[edg[i]] = x;
dfs1(edg[i]);
sz[x] += sz[edg[i]];
if (son[x] == -1 || sz[edg[i]] > sz[son[x]])
son[x] = edg[i];
}
}
void dfs2(int x, int t)
{
top[x] = t;
if (~son[x])
dfs2(son[x], t);
for (int i = hd[x]; ~i; i = nxt[i])
if (edg[i] != son[x] && edg[i] != p[x])
dfs2(edg[i], edg[i]);
}
int lca(int x, int y)
{
while (top[x] != top[y])
{
if (d[top[x]] < d[top[y]])
swap(x, y);
x = p[top[x]];
}
if (d[x] > d[y])
swap(x, y);
return x;
}
void add(int a, int b)
{
nxt[++idx] = hd[a];
hd[a] = idx;
edg[idx] = b;
}
int main()
{
scanf("%d%d%d", &n, &m, &rt);
for (int i = 1; i <= n; i++)
hd[i] = -1;
for (int i = 1, a, b; i < n; i++)
{
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
d[rt] = 1;
dfs1(rt);
dfs2(rt, rt);
for (int a, b; m; m--)
{
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}

Algorithm III Tarjan

离线算法,基本没用,不讲。

复杂度:$O(n + T)$ 。

Algorithm IV ST表

参考资料:mydcwfy

遍历到一个点的时候加在序列末尾,记当前位置为 $id(x)$ ,然后它的一个子树走到另一个子树时,也要将 $x$ 加在序列末尾。

得到的序列可以证明不会超过 $2 n-1$ 。

从 $x$ 到 $y$ 需要从 $lca$ 的一个子树走到另一个子树,所以 $id(x)$ 到 $id(y)$ 之间一定有 $lca$ 。

那么中间深度最小的点就是 $lca$ ,可以用 ST表 维护。

复杂度: $O(n \log n + T)$ 。洛谷模板 $2.65s$ 。

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5e5 + 10, M = 1e6 + 10, K = 20;
int n, q, rt, d[N];
int idx, hd[N], nxt[M], edg[M];
int stmp, id[N], st[N << 1][K], lg[N << 1];
void dfs(int x, int fa)
{
st[++stmp][0] = x;
d[x] = d[fa] + 1;
id[x] = stmp;
for (int i = hd[x]; ~i; i = nxt[i])
if (edg[i] != fa)
{
dfs(edg[i], x);
st[++stmp][0] = x;
}
}
int dmin(int x, int y)
{
return d[x] < d[y] ? x : y;
}
void init()
{
for (int i = 2; i <= stmp; i++)
lg[i] = lg[i >> 1] + 1;
for (int k = 1; (1 << k) <= stmp; k++)
for (int i = 1; i + (1 << k) - 1 <= stmp; i++)
st[i][k] = dmin(st[i][k - 1], st[i + (1 << k - 1)][k - 1]);
}
int lca(int x, int y)
{
x = id[x], y = id[y];
if (x > y)
swap(x, y);
int k = lg[y - x + 1];
return dmin(st[x][k], st[y - (1 << k) + 1][k]);
}
void add(int a, int b)
{
nxt[++idx] = hd[a];
hd[a] = idx;
edg[idx] = b;
}
int main()
{
scanf("%d%d%d", &n, &q, &rt);
for (int i = 1; i <= n; i++)
hd[i] = -1;
for (int i = 1, a, b; i < n; i++)
{
scanf("%d%d", &a, &b);
add(a, b);
add(b, a);
}
dfs(rt, 0);
init();
for (int x, y; q; q--)
{
scanf("%d%d", &x, &y);
printf("%d\n", lca(x, y));
}
return 0;
}