树上最近公共祖先(LCA)问题
参考链接:OI Wiki
倍增LCA
倍增思想
可以参考ST表。
设 $\large f_{x,i}$ 表示 $x$ 的 $2^i$ 级祖先,用 $father_i$ 表示节点 $i$ 的父节点 ,则显然:
$$
\large f_{x,0}=father_x
$$
例如对于这棵树:
$\large f_{9,0}=\normalsize father_9=6$。
节点 $9$ 的 $2^1$ 级祖先 $\large f_{9,1}=father_6=2$。
节点 $11$ 的 $2^2$ 级祖先:
$$
\begin{aligned}
\large f_{11,2}&=\large 1\
&=\large f_{6,1}\
&=\large f_{f_{11,1},1}
\end{aligned}
$$
一个易于发现的事实是,节点 $x$ 的 $2^i$ 级祖先是其 $2^{i-1}$ 级祖先的 $2^{i-1}$ 级祖先。
那么状态转移方程为:
$$
\Large f_{x,i}=f_{f_{x,i-1},i-1}
$$
预处理
同ST表,代码很简单($f[x][i]$ 同上文 $\large f_{x,i}$):
const int N=500000;
//邻接表
struct edge{
int v,r;
}a[2*N+1];
//d:深度,h:链式前向星链头
int h[N+1],d[N+1],f[N+1][(int)log2(N)+1],lg[N+1];
//邻接表建边
void create(int u,int v){
static int top=0;
a[++top]={v,h[u]};
h[u]=top;
}
//q:p的父节点
void dfs(int p,int q){
//基本信息
f[p][0]=q;//2^0=1,即p的父节点,q
d[p]=d[q]+1;//下一层
for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=q)dfs(a[i].v,p);
}
}
void lca_pre(){
dfs(s,0);//s:树根
for(int i=1;i<=n;i++)lg[i]=lg[i/2]+1;
for(int i=1;i<=lg[n];i++){
for(int x=1;x<=n;x++)f[x][i]=f[f[x][i-1]][i-1];
}
}
时间复杂度:$\mathcal O(n\log n)$。
查询
为了便于查找节点 $u,v$ 的最近公共祖先 $LCA(u,v)$,我们可以先使 $u,v$ 跳至同一高度(令 $d[u]>d[v]$),这时,我们便可以使用倍增算法 $\mathcal O(\log n)$ 代替朴素 $\mathcal O(n)$ 向上跳。
类似于二进制,从 $2^{\log_2n+1}$ 到 $2^0$ 依次尝试,如果从 $u$ 跳至 $f[u][i]$ 满足 $d[f[u][i]] \geq d[v]$,那就可以从 $u$ 跳至 $f[u][i]$。
跳至同一高度后,再次倍增跳至相等的下一层,则 $LCA(u,v)=f[u][0]=f[v][0]$。(因为如果直接跳至 $u=v$,可能不是最近公共祖先)。
但是这样存在的问题就是,当 $v$ 为 $u$ 的祖先时,第一次跳完后便有 $u=v$,第二次倍增虽然不会跳,但是返回的 $f[u][0]$ 是错误答案。因此,在第一次倍增跳完后加上特判:if(u==v)return u;
。
查询代码如下:
int lca(int u,int v){
if(d[u]<d[v])swap(u,v);
for(int i=lg[d[u]-d[v]]-1;i>=0;i--){
if(d[f[u][i]]>=d[v])u=f[u][i];
}if(u==v)return u;
for(int i=lg[d[u]]-1;i>=0;i--){
if(f[u][i]!=f[v][i]){
u=f[u][i];v=f[v][i];
}
}return f[u][0];
}
时间复杂度:$\mathcal O(\log n)$。
欧拉序+ST表
欧拉序是什么
DFS序的一种,但是DFS序只会在第一次访问的时候记录,而欧拉序无论访问还是回溯都需要记录。
比如这棵树的欧拉序:绝对不是我懒得画图
欧拉序为 $1,2,5,2,6,9,11,9,6,10,6,2,1,3,7,3,8,3,1,4$。
对于一棵节点数为 $n$ 的树,其欧拉序长度为 $2n-1$。因为共有 $n-1$ 条边,每条边访问两次会往欧拉序中加入两个节点,共计 $2n-2$ 个节点,再加上根节点,共计 $2n-1$ 个。
原理
令 $f_x$ 表示节点 $x$ 在欧拉序中第一次出现的位置。
那么:
$$
LCA(u,v)=\Large \min_{i=f_u}^{f_v}d_{o_i}
$$
其中,$d_{o_i}$ 表示欧拉序中第 $i$ 项的深度,且 $f_u<f_v$(不然直接交换 $u,v$ 即可)。
那么这成为了一个RMQ问题,使用ST表求解即可。
关于其正确性,参考下图:
访问至 $u$ 后,会回溯至 $LCA(u,v)$,随即访问 $v$ 所在子树并最终访问至 $v$。
实现
预处理
先DFS一遍维护基本信息,包括:
- 点 $x$ 的深度 $d_x$;
- 欧拉序第 $i$ 项为 $o_i$;
- 点 $x$ 在欧拉序中第一次出现的位置 $f_x$。
然后对 $o$ 进行ST表求最小值的预处理即可。
代码如下:
const int N=500000,M=500000,N2=2*N;
struct edge{
int v,r;
}a[2*M+1];
int n,m,s,top,h[N+1],d[N+1],lg[N2+1],o[N2+1],st[N2+1][(int)log2(N2+1)+1],rest[N2+1][(int)log2(N2+1)+1],f[N+1];
void create(int u,int v){//链式前向星
static int top=0;
a[++top]={v,h[u]};
h[u]=top;
}
void dfs(int p,int q){
d[p]=d[q]+1;//深度
o[++top]=p;//欧拉序
if(f[p]==0)f[p]=top;//第一次出现的位置
for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=q){
dfs(a[i].v,p);
o[++top]=p;
}
}
}
void st_pre(){
int n2=2*n-1;
for(int i=0;i<=n2;i++)lg[i]=lg[i/2]+1;//常数优化
for(int i=1;i<=top;i++){
st[i][0]=d[o[i]];
rest[i][0]=o[i];
}
for(int i=1;i<=lg[n2];i++){
for(int x=1;x+(1<<i)-1<=n2;x++){
st[x][i]=min(st[x][i-1],st[x+(1<<i-1)][i-1]);
rest[x][i]=(st[x][i]==st[x][i-1]?rest[x][i-1]:rest[x+(1<<i-1)][i-1]);
}
}
}
void lca_pre(){
dfs(s,0);
st_pre();
}
时间复杂度:$\mathcal O(n\log n)$。
注意事项
- 需要注意的是,维护ST表求区间最小值时,还需要维护一个数组记录 $st[x][i]$ 所对应的点 $rest[x][i]$。
- ST表需要维护至欧拉序的长度 $2n-1$,而不是 $n$。
查询
这真的就没什么好说了,上代码:
int lca(int u,int v){
if(f[u]>f[v])swap(u,v);
int s=log2(f[v]-f[u]+1);
return (st[f[u]][s]<st[f[v]-(1<<s)+1][s]?rest[f[u]][s]:rest[f[v]-(1<<s)+1][s]);
}
时间复杂度:$\mathcal O(1)$。
DFS序+ST表
其实,实质上也可以说成压缩欧拉序。
我们可以发现,“欧拉序+ST表”的解决方案预处理时,尽管时间复杂度为 $\mathcal O(n\log n)$,但常数较大。
因为欧拉序长度为 $2n-1$。
那么我们考虑在此基础上进行优化,可以发现欧拉序中会有重复的点,尝试去除这些点,由此,便有了此方案。
同时,由于这是上一个方案的优化版本,并不会进行详细解释。
同样对于这棵树:
其DFS序为:$1,2,5,6,9,11,10,3,7,8,4$,长度为 $n$。
设 $f_x$ 为节点 $x$ 在DFS序中的位置。
对于节点 $u,v$,不妨令 $f_u<f_v$(不然交换)。
考虑到 $u=v$ 时,直接返回 $u$ 作为答案,因此 $f_u<f_v$ 且 $u\ne v$。
$u$ 不为 $v$ 祖先时
考虑DFS遍历时是先从 $LCA(u,v)$ 向下遍历至 $u$,随后回溯至 $LCA(u,v)$ 的包含 $v$ 的子树树根或其他子节点,再下行至 $v$。
那么在区间 $[f_u,f_v]$ 中任意深度最小的点的父节点即 $LCA(u,v)$。
$u$ 为 $v$ 的祖先时
显然,$LCA(u,v)=u$,且此时 $u$ 至 $v$ 为一条下行链。
考虑到此时再在区间 $[f_u,f_v]$ 中查找深度最小节点,必定查找到点 $u$,使最终答案不正确。
那么在区间 $[f_u+1,f_v]$ 中查找即可,因为这样必定查找到 $f_u+1$ 对应点,其父节点即 $u$。
1.中同样可以查询 $[f_u+1,f_v]$,因为 $u$ 不为 $v$ 祖先时,$LCA(u,v)$ 不为 $u$,去除并不影响答案。
事实上,也可以查询区间 $[f_u+1,f_v-1]$,然而在ST表 $\mathcal O(1)$ 查询下,这不重要。
查询代码如下:
int lca(int u,int v){
if(u==v)return u;
if(f[u]>f[v])swap(u,v);
int s=log2(f[v]-(f[u]+1)+1);
return father[(st[f[u]+1][s] < st[f[v]-(1<<s)+1][s] ? rest[f[u]+1][s] : rest[f[v]-(1<<s)+1][s])];
}
仅仅是多了一个 $father$ 数组表示父节点。
同时,原本需要开至 $2n-1$ 大小的 $lg,st,rest$ 等数组可以只需要开 $n$ 个。
查询时间复杂度:$\mathcal O(1)$。
Tarjan 离线算法(DFS+并查集)
思想
问题存储
先一次性读入所有的询问(所有的 $u_i,v_i$)。随后以类似邻接表的方式存储,“建无向边”。
比如,输入:
1 3
2 4
3 5
7 8
8 3
那么建出来的“邻接表”便长这样:(事实上,这样做仅仅是为了能够快速找到有关节点,而又不浪费空间,与邻接表本身在图上的思想无关,请避免误会)
DFS+并查集
记:
- 节点 $x$ 在图中的父节点为 $father_x$;
- 节点 $x$ 在并查集中的父节点为 $f_x$;
当节点 $x$ 需要回溯至 $father_x$ 时,尝试计算LCA的答案并将 $x$ 和 $father_x$ 并入一个集合。即:$f_x=father_x$。
假设当前访问节点 $x=v_i$,那么如果 $u_i$ 已经访问过,则 $LCA(u_i,v_i)=find(u_i)=find(v_i)$,其中 $find(x)$ 表示 $x$ 所在集合的根。
关于正确性:同样地,参考此图。
此时 $u_i,v_i$ 都属于包含 $LCA(u_i,v_i)$ 的集合,则 $find(u_i)=find(v,i)=LCA(u_i,v_i)$。
最后,当DFS结束时,所有答案便离线得出了。
预处理
首先初始化一个并查集,即对于整数 $i\in[1,n]$,使得$f_i=i$。
随后使用类“邻接表”存储询问 $u_i,v_i$。
再调用 tarjan()
:
void tarjan(int x){
//三种状态:0-没有访问到,1-访问到了没有回溯,2-已经回溯
static int vis[N+1];
vis[x]=1;
for(int i=h[x];i>0;i=a[i].r){
if(vis[a[i].v])continue;//其实就是访问到了父节点,跳过
tarjan(a[i].v);
f[a[i].v]=x;//并查集合并
}for(int i=hq[x];i>0;i=q[i].r){
if(q[i].v)ans[q[i].id]=find(q[i].v);//通过id找到对应问题记录答案
}vis[x]=2;
}
最后一个简单的输出:
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
树链剖分 LCA
树链剖分 LCA 在不考虑树链剖分代码长度下,甚至可以说是最简单的。
然而考虑到树链剖分代码冗长,且树链剖分 LCA 完完全全以树链剖分为基础,本文不再赘述。
参见此处。
各种LCA算法的比较
倍增算法
预处理时间复杂度:$\mathcal O(n\log n)$。
查询时间复杂度:$\mathcal O(\log n)$。
空间复杂度:$\mathcal O(n\log n)$。
欧拉序+ST表
预处理时间复杂度:$\mathcal O(2n\log 2n)$。
查询时间复杂度:$\mathcal O(1)$。
空间复杂度:$\mathcal O(2n\log 2n)$。
注:使用 $2n$ 是为了与”DFS序+ST表“形成对比。
DFS序+ST表
预处理时间复杂度:$\mathcal O(n\log n)$。
查询时间复杂度:$\mathcal O(1)$。
空间复杂度:$\mathcal O(n\log n)$。
Tarjan 离线算法
预处理时间复杂度:$\mathcal O(n)$。
求解时间复杂度:$\mathcal O(m\alpha(m+n,n)+n)$。
空间复杂度:$\mathcal O(n+m)$。
并不存在「朴素 Tarjan LCA 算法中使用的并查集性质比较特殊,单次调用
find()
函数的时间复杂度为均摊 $\mathcal O(1)$」这种说法。以下的朴素 Tarjan 实现复杂度为 $\mathcal O(m\alpha(m+n,n)+n)$。如果需要追求严格线性,可以参考 Gabow 和 Tarjan 于 1983 年的论文。其中给出了一种复杂度为 $\mathcal O(n+m)$ 的做法。——OI Wiki
树链剖分 LCA
预处理时间复杂度:$\mathcal O(n)$。
查询时间复杂度:$\mathcal O(\log n)$。
空间复杂度:$\mathcal O(n)$。
汇总
倍增相对而言更加好理解,更加适合初学者,但复杂度较劣。
DFS序+ST表的复杂度较优,代码也并不难理解,适合使用。
欧拉序+ST表不如DFS序+ST表。
Tarjan 离线算法虽然复杂度最优,但常数较大,且可能并不是那么的好理解。
树链剖分除非是题目已经使用了树链剖分,否则不建议使用。
练习题(参考代码)
【模板】最近公共祖先(LCA)
倍增算法
参考代码
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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=500000;
//邻接表:链式前向星
struct edge{
int v,r;
}a[2*N+1];
//d[i]:节点i的深度(第几层),f[i][j]:节点i的2^j级祖先(向上寻找2^j次父节点)
int n,m,s,x,y,h[N+1],d[N+1],f[N+1][32],lg[N+1];
//创建一条边(u,v)
void create(int u,int v){
static int top=0;
a[++top]={v,h[u]};
h[u]=top;
}
//q:p的父节点
void dfs(int p,int q){
//基本信息
f[p][0]=q;//2^0=1,即p的父节点,q
d[p]=d[q]+1;//下一层
for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=q)dfs(a[i].v,p);
}
}
void lca_pre(){
dfs(s,0);
for(int i=1;i<=n;i++)lg[i]=lg[i/2]+1;
for(int i=1;i<=lg[n];i++){
for(int x=1;x<=n;x++)f[x][i]=f[f[x][i-1]][i-1];
}
}
int lca(int u,int v){
if(d[u]<d[v])swap(u,v);
for(int i=lg[d[u]-d[v]]-1;i>=0;i--){
if(d[f[u][i]]>=d[v])u=f[u][i];
}if(u==v)return u;
for(int i=lg[d[u]]-1;i>=0;i--){
if(f[u][i]!=f[v][i]){
u=f[u][i];v=f[v][i];
}
}return f[u][0];
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">n</span><span class="p">,</span><span class="o">&</span><span class="n">m</span><span class="p">,</span><span class="o">&</span><span class="n">s</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">create</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">);</span><span class="n">create</span><span class="p">(</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">);</span>
<span class="p">}</span><span class="n">lca_pre</span><span class="p">();</span>
<span class="k">while</span><span class="p">(</span><span class="n">m</span><span class="o">--</span><span class="p">){</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">lca</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/*fclose(stdin);
fclose(stdout);*/</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
欧拉序+ST表
参考代码
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
75
76
77
78
79
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=500000,M=500000,N2=2*N;
struct edge{
int v,r;
}a[2*M+1];
int n,m,s,top,h[N+1],d[N+1],lg[N2+1],o[N2+1],st[N2+1][(int)log2(N2+1)+1],rest[N2+1][(int)log2(N2+1)+1],f[N+1];
void create(int u,int v){
static int top=0;
a[++top]={v,h[u]};
h[u]=top;
}
void dfs(int p,int q){
d[p]=d[q]+1;
o[++top]=p;
if(f[p]==0)f[p]=top;
for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=q){
dfs(a[i].v,p);
o[++top]=p;
}
}
}
void st_pre(){
int n2=2*n-1;
for(int i=0;i<=n2;i++)lg[i]=lg[i/2]+1;
for(int i=1;i<=top;i++){
st[i][0]=d[o[i]];
rest[i][0]=o[i];
}
for(int i=1;i<=lg[n2];i++){
for(int x=1;x+(1<<i)-1<=n2;x++){
st[x][i]=min(st[x][i-1],st[x+(1<<i-1)][i-1]);
rest[x][i]=(st[x][i]==st[x][i-1]?rest[x][i-1]:rest[x+(1<<i-1)][i-1]);
}
}
}
void lca_pre(){
dfs(s,0);
st_pre();
}
int lca(int u,int v){
if(f[u]>f[v])swap(u,v);
int s=log2(f[v]-f[u]+1);
return (st[f[u]][s]<st[f[v]-(1<<s)+1][s]?rest[f[u]][s]:rest[f[v]-(1<<s)+1][s]);
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">n</span><span class="p">,</span><span class="o">&</span><span class="n">m</span><span class="p">,</span><span class="o">&</span><span class="n">s</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">create</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">);</span><span class="n">create</span><span class="p">(</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">);</span>
<span class="p">}</span><span class="n">lca_pre</span><span class="p">();</span>
<span class="k">while</span><span class="p">(</span><span class="n">m</span><span class="o">--</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">lca</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/*fclose(stdin);
fclose(stdout);*/</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
DFS序+ST表
参考代码
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
75
76
77
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=500000,M=500000;
struct edge{
int v,r;
}a[2*M+1];
int n,m,s,top,h[N+1],d[N+1],lg[N+1],o[N+1],st[N+1][(int)log2(N+1)+1],rest[N+1][(int)log2(N+1)+1],f[N+1],father[N+1];
void create(int u,int v){
static int top=0;
a[++top]={v,h[u]};
h[u]=top;
}
void dfs(int p,int q){
father[p]=q;
d[p]=d[q]+1;
o[++top]=p;
if(f[p]==0)f[p]=top;
for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=father[p])dfs(a[i].v,p);
}
}
void st_pre(){
for(int i=0;i<=n;i++)lg[i]=lg[i/2]+1;
for(int i=1;i<=top;i++){
st[i][0]=d[o[i]];
rest[i][0]=o[i];
}
for(int i=1;i<=lg[n];i++){
for(int x=1;x+(1<<i)-1<=n;x++){
st[x][i]=min(st[x][i-1],st[x+(1<<i-1)][i-1]);
rest[x][i]=(st[x][i]==st[x][i-1]?rest[x][i-1]:rest[x+(1<<i-1)][i-1]);
}
}
}
void lca_pre(){
dfs(s,0);
st_pre();
}
int lca(int u,int v){
if(u==v)return u;
if(f[u]>f[v])swap(u,v);
int s=log2(f[v]-(f[u]+1)+1);
return father[(st[f[u]+1][s] < st[f[v]-(1<<s)+1][s] ? rest[f[u]+1][s] : rest[f[v]-(1<<s)+1][s])];
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">n</span><span class="p">,</span><span class="o">&</span><span class="n">m</span><span class="p">,</span><span class="o">&</span><span class="n">s</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">create</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">);</span><span class="n">create</span><span class="p">(</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">);</span>
<span class="p">}</span><span class="n">lca_pre</span><span class="p">();</span>
<span class="k">while</span><span class="p">(</span><span class="n">m</span><span class="o">--</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">lca</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/*fclose(stdin);
fclose(stdout);*/</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
Tarjan 离线算法(DFS+并查集)
参考代码
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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=5e5,M=5e5;
//邻接表
struct edge{
int v,r;
}a[2*N+1];
struct problem{
int v,r,id;
}q[2*M+1];
int n,m,s,h[N+1],hq[N+1],f[N],ans[N];
void create(int u, int v) {
static int top;
a[++top]={v,h[u]};
h[u]=top;
}
void create2(int u,int v,int id){
static int top;
q[++top]={v,hq[u],id};
hq[u]=top;
}
int find(int x){//路径压缩
if(f[x]!=x)return f[x]=find(f[x]);
return x;
}
void tarjan(int x){
static int vis[N+1];
vis[x]=1;
for(int i=h[x];i>0;i=a[i].r){
if(vis[a[i].v])continue;
tarjan(a[i].v);
f[a[i].v]=x;//加入并查集
}for(int i=hq[x];i>0;i=q[i].r){
if(vis[q[i].v]==2)ans[q[i].id]=find(q[i].v);//记录答案
}vis[x]=2;//已经回溯过
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">n</span><span class="p">,</span><span class="o">&</span><span class="n">m</span><span class="p">,</span><span class="o">&</span><span class="n">s</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="n">f</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">u</span><span class="p">,</span><span class="o">&</span><span class="n">v</span><span class="p">);</span>
<span class="n">create</span><span class="p">(</span><span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">);</span><span class="n">create</span><span class="p">(</span><span class="n">v</span><span class="p">,</span><span class="n">u</span><span class="p">);</span>
<span class="p">}</span><span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">m</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">,</span><span class="o">&</span><span class="n">y</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">x</span><span class="o">==</span><span class="n">y</span><span class="p">)</span><span class="n">ans</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">x</span><span class="p">;</span>
<span class="k">else</span> <span class="n">create2</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">i</span><span class="p">),</span><span class="n">create2</span><span class="p">(</span><span class="n">y</span><span class="p">,</span><span class="n">x</span><span class="p">,</span><span class="n">i</span><span class="p">);</span>
<span class="p">}</span><span class="n">tarjan</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">m</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">ans</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="cm">/*fclose(stdin);
fclose(stdout);*/</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
树链剖分 LCA
参见此处。
[NOIP2013 提高组] 货车运输
思路分析
让每一辆车的载重限制都尽可能大,在图上建最大生成树,然后树上求LCA即可。(本处仅给出倍增算法的参考代码)
参考代码
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=1e4,M=5e4;
struct edge{
int u,v,w;
}x[M+1];
struct edge_tree{
int v,r,w;
}a[2*M+1];
int n,m,u,v,w,q,f[N+1],h[N+1],lg[N+1],d[N+1],pl[N+1][31],dis[N+1][31];
bool vis[N+1];
void create(int u,int v,int w){
static int top=0;
a[++top]={v,h[u],w};
h[u]=top;
}
int find(int x){
if(f[x]!=x)return f[x]=find(f[x]);
return x;
}
void unite(int x,int y){
f[find(x)]=find(y);
}
bool cmp(edge a,edge b){
return a.w>b.w;
}
void Kruskal(){
sort(x+1,x+m+1,cmp);
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1,cnt=0;i<=m&&cnt<n-1;i++){
if(find(x[i].u)!=find(x[i].v)){
unite(x[i].u,x[i].v);
create(x[i].u,x[i].v,x[i].w);
create(x[i].v,x[i].u,x[i].w);
}
}
}
void dfs(int p,int q,int w){
vis[p]=true;
pl[p][0]=q;
dis[p][0]=w;
d[p]=d[q]+1;
for(int i=1;i<=lg[d[p]];i++){
pl[p][i]=pl[pl[p][i-1]][i-1];
dis[p][i]=min(dis[p][i-1],dis[pl[p][i-1]][i-1]);
}for(int i=h[p];i>0;i=a[i].r){
if(a[i].v!=q)dfs(a[i].v,p,a[i].w);
}
}
int lca(int u,int v){
if(find(u)!=find(v))return -1;
int ans=2147483647;
if(d[u]<d[v])swap(u,v);
while(d[u]>d[v]){
ans=min(ans,dis[u][lg[d[u]-d[v]]-1]);
u=pl[u][lg[d[u]-d[v]]-1];
}if(u==v)return ans;
for(int i=lg[d[u]]-1;i>=0;i--){
if(pl[u][i]!=pl[v][i]){
ans=min(ans,min(dis[u][i],dis[v][i]));
u=pl[u][i];
v=pl[v][i];
}
}ans=min(ans,min(dis[u][0],dis[v][0]));
return ans;
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">n</span><span class="p">,</span><span class="o">&</span><span class="n">m</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">m</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">u</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">v</span><span class="p">,</span><span class="o">&</span><span class="n">x</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">w</span><span class="p">);</span>
<span class="n">Kruskal</span><span class="p">();</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="n">lg</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">lg</span><span class="p">[</span><span class="n">i</span><span class="o">/</span><span class="mi">2</span><span class="p">]</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o"><=</span><span class="n">n</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="n">vis</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">==</span><span class="nb">false</span><span class="p">)</span><span class="n">dfs</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span><span class="o">&</span><span class="n">q</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="n">q</span><span class="o">--</span><span class="p">){</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d %d"</span><span class="p">,</span><span class="o">&</span><span class="n">u</span><span class="p">,</span><span class="o">&</span><span class="n">v</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span><span class="n">lca</span><span class="p">(</span><span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">));</span>
<span class="p">}</span>
<span class="cm">/*fclose(stdin);
fclose(stdout);*/</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
树上最近公共祖先(LCA)问题的更多相关文章
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 算法学习笔记(5): 最近公共祖先(LCA)
最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- 最近公共祖先LCA(Tarjan算法)的思考和算法实现
LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...
- 查找最近公共祖先(LCA)
一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...
- 最近公共祖先 LCA 倍增算法
树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...
- 最近公共祖先(LCA)模板
以下转自:https://www.cnblogs.com/JVxie/p/4854719.html 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...
随机推荐
- C# 使用StackExchange.Redis实现分布式锁的两种方式
分布式锁在集群的架构中发挥着重要的作用.以下有主要的使用场景 1.在秒杀.抢购等高并发场景下,多个用户同时下单同一商品,可能导致库存超卖. 2.支付.转账等金融操作需保证同一账户的资金变动是串行执行的 ...
- MySQL的基本语法(增,删,改,查)
MySQL的基本语法(增,删,改,查) MySQL中的(增)操作 创建数据库 CREATE DATABASE 库名; 例如: CREATE DATABASE db; 创建一个名为db的数据库. 创建列 ...
- CSS 魔法与布局技巧
CSS 布局与视觉效果常用实践指南 在我一篇随笔中其实有说到十大布局,里面有提到 flex 布局.grid 布局.响应式布局,不过没有提到容器查询这个,现在说下这三个布局然后穿插下容器查询把. 1️⃣ ...
- 解决MySQL 8.0 设置简单密码报错ERROR 1819 (HY000): Your password does not satisfy the current policy require...
MySQL8.0下设置简单密码出现错误提示:ERROR 1819 (HY000): Your password does not satisfy the current policy requirem ...
- SQL 日常练习 (十四)
最近的项目都比较忙, 没太有时间来做练习, 不过 sql 这块, 还是始终要保持良好的手感, 我已经渐渐感觉到, 随着写得越来越多, 当然不只是在这里, 更多是在工作中, 不过涉及信息安全不能共享. ...
- 在CentOS 7虚拟机上正确安装Redis
在CentOS 7虚拟机上正确安装Redis,可以按照以下步骤进行操作: 更新系统软件包:sudo yum update 安装Redis依赖库:sudo yum install epel-releas ...
- hashicorp/raft模块实现的raft集群存在节点跨集群身份冲突问题
我通过模块github.com/hashicorp/raft使用golang实现了一个raft集群功能,发现如下场景中会遇到一个问题: 测试启动如下2个raft集群,集群名称,和集群node与IP地址 ...
- rancher 卸载后重装报错
报错信息 kubectl create namespace cattle-system Error from server (InternalError): Internal error occurr ...
- 高德地图 MCP,可用 Java SolonMCP 接入(支持 java8, java11, java17, java21)
1.MCP技术概述 1.1 什么是 MCP MCP (Model Control Protocol) 是一种允许大模型与外部工具交互的协议,高德地图基于此协议提供了地图服务能力,使 AI 大模型能够直 ...
- 明明是同一条SQL,为什么有时候走索引a,有时候却走索引b ?
前言 想象你是一家餐厅的服务员,面前有两个菜单: 菜单A:按菜品分类排列(前菜.主菜.甜点) 菜单B:按价格从低到高排列 当顾客说:"我要最便宜的川菜". 你会: 先用菜单B找到所 ...