学习笔记:最近公共祖先
最近公共祖先
最近公共祖先,简称
例题: P3379
倍增做法求
倍增做法是一种在线做法,时间复杂度为:
算法详解
定义
首先我们先
状态转移方程
然后我们再来思考怎么
由于
那么
那么求
我们先让
那么这时
所以状态转移方程就是:
查询
这个过程一共分两步:
我们先假设深度目前深度更大的点为
那么如果不满足这个条件,就先交换两个节点
-
先让
跳到与 深度相同的祖先上:那么这里就可以用多重背包的二进制优化的思想
假设需要跳
步,那么我们就可以先让 跳到 级祖先上,再跳到目前的 的 级祖先上那么
就一共跳了 步 -
再让
和 同时往上跳,直到跳到最近祖先上:但是这样不太好处理,那么我们就可以先让
和 跳到 的下面那一层的节点,再跳到那么我们就可以从二进制下位数高的枚举到位数低的,如果跳完后两个节点不同,那么就说明跳完后一定不会是
或 的祖先,那么就让他们往上跳那么最后返回的结果就是
但是需要注意:有可能执行完第一步后
Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+1;
vector<int> g[N];
int n,m,root,depth[N],fa[N][20];
void dfs(int now,int father) //预处理dpeth和fa数组
{
depth[now]=depth[father]+1;
fa[now][0]=father;
for (int i=1;i<=19;i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
for (int to:g[now])
if (to!=father)
dfs(to,now);
}
int LCA(int x,int y) //倍增求LCA
{
if (depth[x]<depth[y])
swap(x,y);
for (int i=19;i>=0;i--)
if (depth[fa[x][i]]>=depth[y])
x=fa[x][i];
if (x==y)
return x;
for (int i=19;i>=0;i--)
if (fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&root);
for (int i=1,u,v;i<n;i++)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(root,0);
for (int i=1,x,y;i<=m;i++) {
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
return 0;
}
算法求
算法详解
我们先把每个查询得两个节点的对应的节点和查询编号存下来
然后对树进行
- 已经遍历过的点,标记为 2
- 正在搜索的点,标记为 1
- 还未搜索到的点,标记为 0
对于正在搜索的点,我们可以把他们全部合并到他们的根节点上
然后我们再找出与当前正在搜的点
如果对应的
Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+1;
vector<int> g[N];
int fa[N],ans[N],vis[N];
vector< pair<int,int> > Q[N]; //Q[i].first存查询的另外一个点,Q[i].second存查询编号
int find(int x) {
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void tarjan(int x)
{
vis[x]=1;
for (int y:g[x])
if (!vis[y]) {
tarjan(y);
fa[y]=x;
}
vis[x]=2;
for (auto now:Q[x]) {
int y=now.first,id=now.second;
if (vis[y]==2) ans[id]=find(y);
}
}
int main()
{
int n,m,root;
scanf("%d%d%d",&n,&m,&root);
for (int i=1,u,v,val;i<n;i++)
{
scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
for (int i=0,x,y;i<m;i++)
{
scanf("%d%d",&x,&y);
Q[x].push_back(make_pair(y,i));
Q[y].push_back(make_pair(x,i));
}
for (int i=1;i<=n;i++) fa[i]=i;
tarjan(root);
for (int i=0;i<m;i++) printf("%d\n",ans[i]);
return 0;
}
重链剖分求 LCA
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const int N=6e5;
vector<int> g[N];
int depth[N],fa[N];
int size[N],son[N],top[N];
void dfs1(int u,int f)
{
fa[u]=f,size[u]=1;
depth[u]=depth[f]+1;
for (int v:g[u])
if (v!=f) {
dfs1(v,u);
size[u]+=size[v];
if (size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int topf)
{
top[u]=topf;
if (son[u]) dfs2(son[u],topf);
for (int v:g[u])
if (!top[v])
dfs2(v,v);
}
int LCA(int x,int y)
{
while (top[x]!=top[y]) {
if (depth[top[x]]<depth[top[y]]) swap(x,y);
x=fa[top[x]];
}
return depth[x]<depth[y]?x:y;
}
int main()
{
int n,m,s,x,y;
scanf("%d%d%d",&n,&m,&s);
for (int i=1;i<n;i++) {
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
} dfs1(s,0),dfs2(s,s);
while (m--) {
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
return 0;
}
欧拉序求 LCA
先对树跑遍 dfs,求出欧拉序,每次 dfs 到一个节点就把这个节点加入欧拉序,再遍历子节点,dfs 遍历完整棵子树后回到父节点再把父节点加入欧拉序
由于每条边最多进一次出一次,所以欧拉序的长度为
再对欧拉序跑遍 ST 表,处理区间中深度最小的节点
询问时查询两点在欧拉序中第一次出现的位置间深度最小的节点即可
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+1,M=1e6;
int n,m,pos[N],depth[N];
int len,id[M],st[M][20];
vector<int> g[N]; int s;
int read() {
int x=0; char ch=0; while (!isdigit(ch) ) ch=getchar();
while (isdigit(ch) ) x=(x<<3)+(x<<1)+(ch&15),ch=getchar();
return x;
}
void dfs(int u,int fa) {
depth[u]=depth[fa]+1,id[++len]=u,pos[u]=len,st[len][0]=u;
for (int v:g[u]) if (v^fa) dfs(v,u),id[++len]=u,st[len][0]=u;
}
void init()
{
for (int j=1;j<20;j++)
for (int i=1;i+(1<<j)-1<=len;i++) {
int x=st[i][j-1],y=st[i+(1<<j-1) ][j-1];
st[i][j]=depth[x]<depth[y]?x:y;
}
}
int LCA(int x,int y)
{
int l=pos[x],r=pos[y];
if (l>r) swap(l,r); int k=log2(r-l+1);
x=st[l][k],y=st[r-(1<<k)+1][k];
return depth[x]<depth[y]?x:y;
}
int main()
{
n=read(),m=read(),s=read();
for (int i=1,x,y;i<n;i++)
{
x=read(),y=read();
g[x].push_back(y);
g[y].push_back(x);
}
dfs(s,0),init();
while (m--) printf("%d\n",LCA(read(),read() ) );
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具