参考链接: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$。

  1. $u$ 不为 $v$ 祖先时

    考虑DFS遍历时是先从 $LCA(u,v)$ 向下遍历至 $u$,随后回溯至 $LCA(u,v)$ 的包含 $v$ 的子树树根或其他子节点,再下行至 $v$。

    那么在区间 $[f_u,f_v]$ 中任意深度最小的点的父节点即 $LCA(u,v)$。

  2. $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">&amp;</span><span class="n">n</span><span class="p">,</span><span class="o">&amp;</span><span class="n">m</span><span class="p">,</span><span class="o">&amp;</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">&lt;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">n</span><span class="p">,</span><span class="o">&amp;</span><span class="n">m</span><span class="p">,</span><span class="o">&amp;</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">&lt;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">n</span><span class="p">,</span><span class="o">&amp;</span><span class="n">m</span><span class="p">,</span><span class="o">&amp;</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">&lt;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&amp;</span><span class="n">n</span><span class="p">,</span><span class="o">&amp;</span><span class="n">m</span><span class="p">,</span><span class="o">&amp;</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">&lt;=</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">&lt;</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">&amp;</span><span class="n">u</span><span class="p">,</span><span class="o">&amp;</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">&lt;=</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">&amp;</span><span class="n">x</span><span class="p">,</span><span class="o">&amp;</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">&lt;=</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">&amp;</span><span class="n">n</span><span class="p">,</span><span class="o">&amp;</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">&lt;=</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">&amp;</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">&amp;</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">&amp;</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">&lt;=</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">&lt;=</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">&amp;</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">&amp;</span><span class="n">u</span><span class="p">,</span><span class="o">&amp;</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)问题的更多相关文章

  1. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  2. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  3. 算法学习笔记(5): 最近公共祖先(LCA)

    最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...

  4. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  5. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  6. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  7. 最近公共祖先LCA(Tarjan算法)的思考和算法实现

    LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了f ...

  8. 查找最近公共祖先(LCA)

    一.问题 求有根树的任意两个节点的最近公共祖先(一般来说都是指二叉树).最近公共祖先简称LCA(Lowest Common Ancestor).例如,如下图一棵普通的二叉树. 结点3和结点4的最近公共 ...

  9. 最近公共祖先 LCA 倍增算法

          树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...

  10. 最近公共祖先(LCA)模板

    以下转自:https://www.cnblogs.com/JVxie/p/4854719.html 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖 ...

随机推荐

  1. C# 使用StackExchange.Redis实现分布式锁的两种方式

    分布式锁在集群的架构中发挥着重要的作用.以下有主要的使用场景 1.在秒杀.抢购等高并发场景下,多个用户同时下单同一商品,可能导致库存超卖. 2.支付.转账等金融操作需保证同一账户的资金变动是串行执行的 ...

  2. MySQL的基本语法(增,删,改,查)

    MySQL的基本语法(增,删,改,查) MySQL中的(增)操作 创建数据库 CREATE DATABASE 库名; 例如: CREATE DATABASE db; 创建一个名为db的数据库. 创建列 ...

  3. CSS 魔法与布局技巧

    CSS 布局与视觉效果常用实践指南 在我一篇随笔中其实有说到十大布局,里面有提到 flex 布局.grid 布局.响应式布局,不过没有提到容器查询这个,现在说下这三个布局然后穿插下容器查询把. 1️⃣ ...

  4. 解决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 ...

  5. SQL 日常练习 (十四)

    最近的项目都比较忙, 没太有时间来做练习, 不过 sql 这块, 还是始终要保持良好的手感, 我已经渐渐感觉到, 随着写得越来越多, 当然不只是在这里, 更多是在工作中, 不过涉及信息安全不能共享. ...

  6. 在CentOS 7虚拟机上正确安装Redis

    在CentOS 7虚拟机上正确安装Redis,可以按照以下步骤进行操作: 更新系统软件包:sudo yum update 安装Redis依赖库:sudo yum install epel-releas ...

  7. hashicorp/raft模块实现的raft集群存在节点跨集群身份冲突问题

    我通过模块github.com/hashicorp/raft使用golang实现了一个raft集群功能,发现如下场景中会遇到一个问题: 测试启动如下2个raft集群,集群名称,和集群node与IP地址 ...

  8. rancher 卸载后重装报错

    报错信息 kubectl create namespace cattle-system Error from server (InternalError): Internal error occurr ...

  9. 高德地图 MCP,可用 Java SolonMCP 接入(支持 java8, java11, java17, java21)

    1.MCP技术概述 1.1 什么是 MCP MCP (Model Control Protocol) 是一种允许大模型与外部工具交互的协议,高德地图基于此协议提供了地图服务能力,使 AI 大模型能够直 ...

  10. 明明是同一条SQL,为什么有时候走索引a,有时候却走索引b ?

    前言 想象你是一家餐厅的服务员,面前有两个菜单: 菜单A:按菜品分类排列(前菜.主菜.甜点) 菜单B:按价格从低到高排列 当顾客说:"我要最便宜的川菜". 你会: 先用菜单B找到所 ...