【LCA专题】各种LCA求法

这篇博客,本蒟蒻会详解各种lca求法,朴素和倍增会后续增加上去

欧拉序列+RMQ 【O(n+m+nlogn+q)】

这种求法对于询问次数特别的多的情况比较好用,有一些出题人可能会特意卡这个,让你必须要用欧拉序列

原理:

把每个点搜索到的顺序记录下来,形成一个序列(就是欧拉序列),然后rmq深度,最后O(1)输出

看这个例子

很明显记录序号+rmq深度就好

它的核心在这里

 1 void dfs(int now,int fa,int deep){
 2     dfn[now]=++tt;dep[now]=deep;
 3     rmq[tt][0]=now;//这里记录了一次
 4     for(int i=head[now];i!=-1;i=edge[i].nxt){
 5         int to=edge[i].to;
 6         if(to==fa)continue;
 7         dfs(to,now,deep+1);
 8         rmq[++tt][0]=now;//这里又记录了一次
 9     }
10 }

会发现记录了两次,这样rmq数组就能把整个搜索顺序都记录下来

rmq就很简单了,就是用dep来rmq

1 for(int j=1;j<=19;j++)
2     {
3         for(int i=1;i+(1<<j)-1<=tt;i++)
4         {
5             rmq[i][j]=dep[rmq[i][j-1]]<dep[rmq[i+(1<<(j-1))][j-1]]?rmq[i][j-1]:rmq[i+(1<<(j-1))][j-1];
6         }
7     }

 最后输出就好

但是有一点玄学的地方,就是rmq数组不能开太大,本人就是开大了之后就T了,用多大开多大就好

 1 //欧拉序列 
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define N 500111
 6 #define clear(a,val) memset(a,val,sizeof(a))
 7 using namespace std;
 8 struct star{int to,nxt;}edge[N*2];
 9 int n,m,u,v,s,cnt=1,tt;
10 int head[N],dep[N],rmq[2*N][20],dfn[N],lg2[2*N];
11 inline void add(int u,int v){
12     edge[cnt].nxt=head[u];
13     edge[cnt].to=v;
14     head[u]=cnt++;
15 }
16 void dfs(int now,int fa,int deep){
17     dfn[now]=++tt;dep[now]=deep;
18     rmq[tt][0]=now;
19     for(int i=head[now];i!=-1;i=edge[i].nxt){
20         int to=edge[i].to;
21         if(to==fa)continue;
22         dfs(to,now,deep+1);
23         rmq[++tt][0]=now;
24     }
25 }
26 int main()
27 {
28     clear(head,-1);
29     scanf("%d%d%d",&n,&m,&s);
30     for(int i=1;i<n;i++){
31         scanf("%d%d",&u,&v);
32         add(u,v),add(v,u);
33     }dep[s]=1,dfs(s,-1,1);
34     for(int i=2;i<=tt;i++)
35     {
36         lg2[i]=lg2[i>>1]+1;
37     }
38     for(int j=1;j<=19;j++)
39     {
40         for(int i=1;i+(1<<j)-1<=tt;i++)
41         {
42             rmq[i][j]=dep[rmq[i][j-1]]<dep[rmq[i+(1<<(j-1))][j-1]]?rmq[i][j-1]:rmq[i+(1<<(j-1))][j-1];
43         }
44     }
45     for(int i=1;i<=m;i++)
46     {
47         scanf("%d%d",&u,&v);
48         int l=dfn[u],r=dfn[v];
49         if(l>r)swap(l,r);
50         int lg=lg2[r-l+1];
51         printf("%d
",dep[rmq[l][lg]]<dep[rmq[r-(1<<lg)+1][lg]]?rmq[l][lg]:rmq[r-(1<<lg)+1][lg]);
52     }
53     return 0;
54 }
euler

Trajan【离线O(n+m+q)】

这是一个很神的算法,但是有些题可能会卡离线

它的思路就是对询问进行用链式前向星离散化(双向边),然后在做的过程中用并查集维护父亲,然后如果发现搜到x时,有询问(x,y),而且y已经搜过了(也就是更新过父亲了),这组询问的答案就是find(y)

核心

 1 void tarjan(int now,int fa)
 2 {
 3     f[now]=now;
 4     for(int i=head[now];i!=-1;i=edge[i].nxt)
 5     {
 6         int to=edge[i].to;
 7         if(to==fa)continue;
 8         if(!vis[to])tarjan(to,now),f[to]=now;
 9     }
10     vis[now]=1;
11     for(int i=hd[now];i!=-1;i=ee[i].nxt)
12     {
13         int to=ee[i].to;
14         if(vis[to])ans[ee[i].num]=find(to);
15     }
16 }

有一个注意的地方,就是f要放在循环里进行更新这个是我右桌大佬亲身实WA的,放在外面如果更新它和它儿子的答案就会错(yy一下就知道)

离散化的时候加一个问题编号,处理起来非常方便

 1 //tarjan
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define N 500111
 6 #define clear(a,val) memset(a,val,sizeof(a))
 7 using namespace std;
 8 struct star{int to,nxt;}edge[N*2];
 9 struct moon{int to,nxt,num;}ee[N*2];
10 int n,m,u,v,s,cnt=1,ct=1;
11 int head[N],hd[N],f[N],ans[N];
12 bool vis[N];
13 int find(int x){return x==f[x]?f[x]:f[x]=find(f[x]);}
14 inline void add(int u,int v){
15     edge[cnt].nxt=head[u];
16     edge[cnt].to=v;
17     head[u]=cnt++;
18 }
19 inline void ad(int u,int v,int num){
20     ee[ct].nxt=hd[u];
21     ee[ct].to=v;
22     ee[ct].num=num;
23     hd[u]=ct++;
24 }
25 void tarjan(int now,int fa)
26 {
27     f[now]=now;
28     for(int i=head[now];i!=-1;i=edge[i].nxt)
29     {
30         int to=edge[i].to;
31         if(to==fa)continue;
32         if(!vis[to])tarjan(to,now),f[to]=now;
33     }
34     vis[now]=1;
35     for(int i=hd[now];i!=-1;i=ee[i].nxt)
36     {
37         int to=ee[i].to;
38         if(vis[to])ans[ee[i].num]=find(to);
39     }
40 }
41 int main()
42 {
43     clear(head,-1),clear(hd,-1);
44     scanf("%d%d%d",&n,&m,&s);
45     for(int i=1;i<n;i++)
46         scanf("%d%d",&u,&v),add(u,v),add(v,u);
47     for(int i=1;i<=m;i++)
48         scanf("%d%d",&u,&v),ad(u,v,i),ad(v,u,i);
49     tarjan(s,-1);
50     for(int i=1;i<=m;i++)
51         printf("%d
",ans[i]);
52     return 0;
53 }
tarjan

树链剖分【O(n+m+qlogn)】

这个算法是非常快的,也是最常用的!!而且还好写,所以一定要记住

它的原理呢

关于重儿子轻儿子重链轻链的知识可以百度

首先这个预处理

 1 int init(int now,int fa)
 2 {
 3     size[now]=1;
 4     int maxson=-1;
 5     for(int i=head[now];i!=-1;i=edge[i].nxt)
 6     {
 7         int to=edge[i].to;
 8         if(to==fa)continue;
 9         dep[to]=dep[now]+1,f[to]=now;
10         size[now]+=init(to,now);
11         if(size[to]>maxson)maxson=size[to],son[now]=to;
12     }
13     return size[now];
14 }

 这里的son存的就是每个节点的重儿子,然后size的维护之前博客有写过,f的记录是必要的,因为查询的时候会用到,

然后第二个重标记过程

 1 void remark(int now,int topf,int fa)
 2 {
 3     id[now]=++tt;
 4     top[now]=topf;
 5     if(!son[now])return;
 6     remark(son[now],topf,now);
 7     for(int i=head[now];i!=-1;i=edge[i].nxt)
 8     {
 9         int to=edge[i].to;
10         if(!id[to] && to!=fa)remark(to,to,now);
11     }
12 }

 这个比较好理解,先搜重儿子(这样会使重链编号是连续的),topf维护的是重链的起点,fa用来防止双向边死循环

然后代码

 1 //树链剖分 
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define N 500111
 6 #define clear(a,val) memset(a,val,sizeof(a))
 7 using namespace std;
 8 struct star{int to,nxt;}edge[N*2];
 9 int n,m,u,v,s,cnt=1,tt;
10 int head[N],dep[N],size[N],f[N],son[N],id[N],top[N];
11 inline void add(int u,int v){
12     edge[cnt].nxt=head[u];
13     edge[cnt].to=v;
14     head[u]=cnt++;
15 }
16 int init(int now,int fa)
17 {
18     size[now]=1;
19     int maxson=-1;
20     for(int i=head[now];i!=-1;i=edge[i].nxt)
21     {
22         int to=edge[i].to;
23         if(to==fa)continue;
24         dep[to]=dep[now]+1,f[to]=now;
25         size[now]+=init(to,now);
26         if(size[to]>maxson)maxson=size[to],son[now]=to;
27     }
28     return size[now];
29 }
30 void remark(int now,int topf,int fa)
31 {
32     id[now]=++tt;
33     top[now]=topf;
34     if(!son[now])return;
35     remark(son[now],topf,now);
36     for(int i=head[now];i!=-1;i=edge[i].nxt)
37     {
38         int to=edge[i].to;
39         if(!id[to] && to!=fa)remark(to,to,now);
40     }
41 }
42 int ask(int u,int v)
43 {
44     while(top[u]!=top[v])
45     {
46         if(dep[top[u]]<dep[top[v]])swap(u,v);
47         u=f[top[u]];
48     }
49     if(dep[u]>dep[v])swap(u,v);
50     return u;
51 }
52 int main()
53 {
54     clear(head,-1);
55     scanf("%d%d%d",&n,&m,&s);
56     for(int i=1;i<n;i++)
57         scanf("%d%d",&u,&v),add(u,v),add(v,u);
58     dep[s]=1,top[s]=1,init(s,-1),remark(s,s,-1);
59     for(int i=1;i<=m;i++)
60         scanf("%d%d",&u,&v),printf("%d
",ask(u,v));
61     return 0;
62 }
树链剖分
 1 //欧拉序列 
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define N 500111
 6 #define clear(a,val) memset(a,val,sizeof(a))
 7 using namespace std;
 8 struct star{int to,nxt;}edge[N*2];
 9 int n,m,u,v,s,cnt=1,tt;
10 int head[N],dep[N],rmq[2*N][20],dfn[N],lg2[2*N];
11 inline void add(int u,int v){
12     edge[cnt].nxt=head[u];
13     edge[cnt].to=v;
14     head[u]=cnt++;
15 }
16 void dfs(int now,int fa,int deep){
17     dfn[now]=++tt;dep[now]=deep;
18     rmq[tt][0]=now;
19     for(int i=head[now];i!=-1;i=edge[i].nxt){
20         int to=edge[i].to;
21         if(to==fa)continue;
22         dfs(to,now,deep+1);
23         rmq[++tt][0]=now;
24     }
25 }
26 int main()
27 {
28     clear(head,-1);
29     scanf("%d%d%d",&n,&m,&s);
30     for(int i=1;i<n;i++){
31         scanf("%d%d",&u,&v);
32         add(u,v),add(v,u);
33     }dep[s]=1,dfs(s,-1,1);
34     for(int i=2;i<=tt;i++)
35     {
36         lg2[i]=lg2[i>>1]+1;
37     }
38     for(int j=1;j<=19;j++)
39     {
40         for(int i=1;i+(1<<j)-1<=tt;i++)
41         {
42             rmq[i][j]=dep[rmq[i][j-1]]<dep[rmq[i+(1<<(j-1))][j-1]]?rmq[i][j-1]:rmq[i+(1<<(j-1))][j-1];
43         }
44     }
45     for(int i=1;i<=m;i++)
46     {
47         scanf("%d%d",&u,&v);
48         int l=dfn[u],r=dfn[v];
49         if(l>r)swap(l,r);
50         int lg=lg2[r-l+1];
51         printf("%d
",dep[rmq[l][lg]]<dep[rmq[r-(1<<lg)+1][lg]]?rmq[l][lg]:rmq[r-(1<<lg)+1][lg]);
52     }
53     return 0;
54 }
欧拉序列
 1 //tarjan
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #define N 500111
 6 #define clear(a,val) memset(a,val,sizeof(a))
 7 using namespace std;
 8 struct star{int to,nxt;}edge[N*2];
 9 struct moon{int to,nxt,num;}ee[N*2];
10 int n,m,u,v,s,cnt=1,ct=1;
11 int head[N],hd[N],f[N],ans[N];
12 bool vis[N];
13 int find(int x){return x==f[x]?f[x]:f[x]=find(f[x]);}
14 inline void add(int u,int v){
15     edge[cnt].nxt=head[u];
16     edge[cnt].to=v;
17     head[u]=cnt++;
18 }
19 inline void ad(int u,int v,int num){
20     ee[ct].nxt=hd[u];
21     ee[ct].to=v;
22     ee[ct].num=num;
23     hd[u]=ct++;
24 }
25 void tarjan(int now,int fa)
26 {
27     f[now]=now;
28     for(int i=head[now];i!=-1;i=edge[i].nxt)
29     {
30         int to=edge[i].to;
31         if(to==fa)continue;
32         if(!vis[to])tarjan(to,now),f[to]=now;
33     }
34     vis[now]=1;
35     for(int i=hd[now];i!=-1;i=ee[i].nxt)
36     {
37         int to=ee[i].to;
38         if(vis[to])ans[ee[i].num]=find(to);
39     }
40 }
41 int main()
42 {
43     clear(head,-1),clear(hd,-1);
44     scanf("%d%d%d",&n,&m,&s);
45     for(int i=1;i<n;i++)
46         scanf("%d%d",&u,&v),add(u,v),add(v,u);
47     for(int i=1;i<=m;i++)
48         scanf("%d%d",&u,&v),ad(u,v,i),ad(v,u,i);
49     tarjan(s,-1);
50     for(int i=1;i<=m;i++)
51         printf("%d
",ans[i]);
52     return 0;
53 }
tarjan
 1 //N<=500000,M<=500000
 2 #include<bits/stdc++.h>
 3 #define clear(a,val) memset(a,val,sizeof(a));
 4 #define N 500010
 5 #define M 500010
 6 using namespace std;
 7 struct star{
 8     int to,next;
 9 }edge[N*2];
10 int cnt=1,head[N],f[N][20],deep[N];
11 void add(int u,int v)
12 {
13     edge[cnt].next=head[u];    
14     edge[cnt].to=v;
15     head[u]=cnt++;
16 }
17 void dfs(int now,int fa,int dep)
18 {
19     deep[now]=dep;f[now][0]=fa;
20     for(int i=head[now];i!=-1;i=edge[i].next)
21     {
22         int to=edge[i].to;
23         if(to!=fa)dfs(to,now,dep+1);
24     }
25 }
26 int lca(int x,int y)
27 {
28     if(deep[x]<deep[y])swap(x,y);
29     for(int i=16;i>=0;i--)
30     {
31         if(deep[f[x][i]]>=deep[y])x=f[x][i];
32     }
33     if(x==y)return x;
34     for(int i=16;i>=0;i--)
35     {
36         if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
37     }
38     return f[x][0];
39 }
40 int main()
41 {
42     clear(head,-1);
43     int n,m,root;
44     scanf("%d%d%d",&n,&m,&root);
45     for(int i=1;i<n;i++)
46     {
47         int x,y;
48         scanf("%d%d",&x,&y);
49         add(x,y);add(y,x);
50     }deep[root]=1;
51     dfs(root,0,1);
52     for(int j=1;j<=16;j++)
53     {
54         for(int i=1;i<=n;i++)
55         {
56             f[i][j]=f[f[i][j-1]][j-1];
57         }
58     }
59     for(int i=1;i<=m;i++)
60     {
61         int a,b;
62         scanf("%d%d",&a,&b);
63         printf("%d
",lca(a,b));
64     }
65     return 0;
66 }
倍增
原文地址:https://www.cnblogs.com/Qin-Wei-Kai/p/10179773.html