注:求解LCA有至少7种做法,如果让我全部写出来,我会死掉的。这里我只讲朴素和倍增
注:抄别人的代码不是一个好习惯 我不会告诉你这个代码我可以弄了一点点失误进去
版权声明:倍增代码使用的是李白莘莘学子的代码,原文点这里
下面列举一下LCA的做法:朴素算法,倍增,RMQ,用欧拉序列转化为RMQ ,太监(tarjan)和动态树(看这里有惊喜)
RMQ以后将分块时可能会去提(前提我记得)(180+行),太监其中有dfs序和邻接链表,不讲(90+行)。树剖以后讲到会提(很快就讲)
//朴素算法
#include<bits/stdc++.h>
#define MAXN 100100
using namespace std
int n,head[MAXN],dep[MAXN],cnt=0,q,fa[MAXN];
struct edge
{
int nxt,to;
}e[MAXN];
void add(int u,int v)
{
e[++cnt].nxt=head[u];
e[cnt].to=v;
head[u]=cnt;
}
void dep_ccl(int u,int f)//预处理fa[]数组及深度
{
dep[u]=dep[f]+1;//原点深度等于他爸的深度加一
fa[u]=f;
for(int i=head[u];i!=0;i=e[i].nxt)//基操遍历
{
int v=e[i].to;
if(v!=f)dep_ccl(v,u);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(1,0);
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
ans=0;
int a,b;
scanf("%d%d",&a,&b);
while(a!=b)//LCA
{
if(dep[a]>=dep[b])a=fa[a];
else b=fa[b];
}
cout<<a<<endl;
}
return 0;
}
//倍增
#include<include>//万能头
#define MAXN 200200
using namespace std;
int n,m,s,cnt=0,head[MAXN],dep[MAXN],f[MAXN][23];
int a,a;
struct edge{
int next,to;
}e[4*MAXN]
void e_add(int u,int v)//链式前向星存图
{
cnt++;
e[cnt].next=head[u];e[cnt].to=v;head[u]=cnt;
e[++cnt].next=head[v];e[cnt].to=u;head[v]=cnt;
}
void dfs(int u,int father)//对应深搜预处理f数组
{
dep[u]=dep[father]+1;
for(int i=1;(1<<i)<=dep[u];i++)//预处理f数组
{
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=head[u];i;i=e[i].next)//遍历树
{
int v=e[i].to;
if(v==father)continue;//双向图需要判断是不是父亲节点
f[v][0]=u;
dfs(v,u);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--)//从大到小枚举
{
if(f[x][i]!=f[y][i])//尽可能接近
{
x=f[x][i];y=f[y][i];
}
}
return f[x][0];//f[y][0]也ok
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
scanf("%d",&a1);scanf("%d",&a2);
e_add(a1,a2);//链式前向星存图
}
dfs(s,0);//预处理
for(int i=1;i<=m;i++)
{
scanf("%d %d",&a1,&a2);
printf("%d
",lca(a1,a2));//求两个节点的LCA
}
}
首先,是我们的朴素算法O(n^2)。
首先,将树上每个节点的深度预处理出来,还有他们的霸霸预处理出来。
方法:爆搜。开两个函数变量:u(遍历到的节点)和f(u他爸)。将整棵树遍历一遍。
遍历过程中,将fa[u](即u他的fuqin节点)赋值为f,dep[u](即u的深度)赋值为dep[f]+1(即u他爸的深度,以前遍历出来过)
然后在主函数中,定义两个指针a,b,最开始的时候指向要求LCA的那两个点。
每次让更深的那个指针往上跳一个点。即a=fa[a]||b=fa[b]
然后在两指针指向同一个点时,这个点就是他们的LCA,原因你自己想想。这不是废话吗
倍增,就是在朴素算法的基础上,呈倍 增上去。同样,有一个fa数组,需要预处理出来。
其中fa[i][j]表示i的第(2^j)个祖先。利用fa的定义(即fa[i][j]表示i的第(2^j)个祖先)用循环预处理出fa数组。
其中有个地方做一点点解释
for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=20;i>=0;i--)//从大到小枚举
{
if(f[x][i]!=f[y][i])//尽可能接近
{
x=f[x][i];y=f[y][i];
}
}
这里i为什么是20到1呢?先看代码
我们发现f的第二位的下标都是i,意味着一定是求某个数的(2^i)个祖先。
如果你担心,你尽可以开31->1甚至63->1,不过一般题目不会给那么大的数据,一般是2^20左右的,这就是这样来的。