先上一个洛谷的模板题,一下模板皆适用,为AC代码。
https://www.luogu.org/problemnew/show/P3379
1.tarjan+并查集 源于https://blog.csdn.net/li1615882553/article/details/79762771
#include<cstdio>
const int N = 5e5+10;
struct hehe{
int next;
int to;
int lca;
};
hehe edge[N<<1];//树的链表
hehe qedge[N<<1];//需要查询LCA的两节点的链表
int n,m,p,x,y;
int num_edge,num_qedge,head[N<<1],qhead[N];
int father[N];
int visit[N];//判断是否被找过
void add_edge(int from,int to){//建立树的链表
edge[++num_edge].next=head[from];
edge[num_edge].to=to;
head[from]=num_edge;
}
void add_qedge(int from,int to){//建立需要查询LCA的两节点的链表
qedge[++num_qedge].next=qhead[from];
qedge[num_qedge].to=to;
qhead[from]=num_qedge;
}
int find(int z){//找爹函数
if(father[z]!=z)
father[z]=find(father[z]);
return father[z];
}
int dfs(int x){//把整棵树的一部分看作以节点x为根节点的小树
father[x]=x;//由于节点x被看作是根节点,所以把x的father设为它自己
visit[x]=1;//标记为已被搜索过
for(int k=head[x];k;k=edge[k].next)//遍历所有与x相连的节点
if(!visit[edge[k].to]){//若未被搜索
dfs(edge[k].to);//以该节点为根节点搞小树
father[edge[k].to]=x;//把x的孩子节点的father重新设为x
}
for(int k=qhead[x];k;k=qedge[k].next)//搜索包含节点x的所有询问
if(visit[qedge[k].to]){//如果另一节点已被搜索过
qedge[k].lca=find(qedge[k].to);//把另一节点的祖先设为这两个节点的最近公共祖先
if(k%2)//由于将每一组查询变为两组,所以2n-1和2n的结果是一样的
qedge[k+1].lca=qedge[k].lca;
else
qedge[k-1].lca=qedge[k].lca;
}
}
int main(){
scanf("%d%d%d",&n,&m,&p);//输入节点数,查询数和根节点
for(int i=1;i<n;++i){
scanf("%d%d",&x,&y);//输入每条边
add_edge(x,y);
add_edge(y,x);
}
for(int i=1;i<=m;++i){
scanf("%d%d",&x,&y);//输入每次查询,考虑(u,v)时若查找到u但v未被查找,所以将(u,v)(v,u)全部记录
add_qedge(x,y);
add_qedge(y,x);
}
dfs(p);//进入以p为根节点的树的深搜
for(int i=1;i<=m;i++)
printf("%d\n",qedge[i*2].lca);//两者结果一样,只输出一组即可
return 0;
}
2. st+dfs思想 st表可离线求数列任意区间最大最小值。LCA基于将树编号后任意两点LCA就是他们之间点编号的最小值。
/*
st表+dfs 求LCA
先对树链进行搜索编号,
利用了两个节点间的公共
祖先就是他们之间编号的最小值的原理。
*/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 500010;
int n, m, s, fp[MAXN], id[MAXN], pn, an, dp[MAXN*2][21];
int head[MAXN<<1], to[MAXN*2], nxt[MAXN*2], cnt;
inline void add(int u, int v) {
to[++cnt] = v;
nxt[cnt] = head[u];
head[u] = cnt;
}
void dfs(int u, int f) {
int i, c;
id[c = ++pn] = u;
dp[fp[u] = ++an][0] = c;
for(i = head[u]; i; i = nxt[i]) {
if(to[i] == f) continue;
dfs(to[i], u);
dp[++an][0] = c;
}
}
void Sparse_Table(){
//st表精华
int i, j;
for(j = 1; (1<<j) <= an; j++)
for(i = 1; i+(1<<j)-1 <= an; i++)
dp[i][j] = min(dp[i][j-1], dp[i+(1<<(j-1))][j-1]);
}
inline int LCA(int l, int r) {
int i, k = (int) (log(r-l+1)/log(2));
return id[min(dp[l][k], dp[r-(1<<k)+1][k])];
}
int main() {
int i, u, v;
cin>>n>>m>>s;
for(i = 1; i < n; i++) {
scanf("%d%d",&u,&v);
add(u, v);add(v, u);
}
dfs(s, -1);
Sparse_Table();
for(i = 1; i <= m; i++) {
//cin>>u>>v;
scanf("%d%d",&u,&v);
if(fp[u]>fp[v]) swap(u, v);
printf("%d\n", LCA(fp[u], fp[v]));
}
return 0;
}
3.倍增法求LCA 在线查询,适用性更广。
//倍增法在线查询LCA
#include<bits/stdc++.h>
using namespace std;const int N=5e5+10;
int n,m,s,x,y,f[N][18],bin[20],d[N];
int head[N<<1],to[N<<1],nxt[N<<1],tot;
void add(int u,int v){ //简单前向星建边
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
}
// 搜索初始化
void dfs(int x){//BIN存储2的几次方
//这步为何?
for(int i=1;bin[i]<=d[x];i++) f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nxt[i]){
if(to[i]==f[x][0]) continue;
d[to[i]]=d[x]+1;f[to[i]][0]=x;dfs(to[i]);
}
}
inline int lca(int x,int y){
if(d[x]<d[y]) swap(x,y);
int t=d[x]-d[y];
//下面操作保证搜到同层次
for(int i=0;bin[i]<=t;i++) if(bin[i]&t) x=f[x][i];
for(int i=17;i>=0;i--){
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
}
if(x==y) return x;return f[x][0];
}
int main(){
bin[0]=1;
for(int i=1;i<20;i++) bin[i]=bin[i-1]<<1;
cin>>n>>m>>s;
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
dfs(s);
while(m--){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
OJXK 。 感谢吴大佬支持!!!
4.罗大佬发明了树剖求LCA,效率优于上述3个,代码写得较早,各位大佬可自行查阅。