LCA最小公共祖先(倍增法)模板

//LCA找最近公共祖先,被增法模板 
//f[y][j]的意思是从y节点走2的j次方走到的节点 
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int d[N],dep[10],f[N][10];//d[N]记录每个节点的深度,dep记录每层节点个数 
int n,u,v,t,tot=0,ans,lca;
int head[N],Next[2*N],ver[2*N];
void add(int x,int y)
{
	ver[++tot]=y;
	Next[tot]=head[x];
	head[x]=tot;
}//领接表存边 
void bfs()
{
	queue<int> q;
	d[1]=1;
	dep[1]++;
	q.push(1);
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		for(int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if(d[y]) continue;
			d[y]=d[x]+1;
			dep[d[y]]++;//存储每行有几个结点
			f[y][0]=x;//y的父节点是x 
			for(int j=1;j<=t;j++)//遍历每层 
			{
				f[y][j]=f[f[y][j-1]][j-1];
			 } 
			 q.push(y);
		}
	}
}//预处理 
int LCA(int x,int y)//s树上倍增法 
{
	if(d[x]>d[y]) swap(x,y);//保证x深度小于y
	for(int i=t;i>=0;i--)//y向上跳,跳到x同层 
	{
		if(d[f[y][i]]>=d[x])//y点还在x下面,可以跳
		{
			y=f[y][i];//更新跳后y的位置 
		 } 
	 } 
	 if(x==y) return x;//能跳到同一个点,找到最小公共祖先
	for(int i=t;i>=0;i--)//x,y同层,一起向上跳 保证一直不相等 
	{//跳到同一个节点时可能出了最小公共祖先 
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];//更新 
		}
	} 
	return f[x][0];//当前节点的父节点就是 lca
}
int main()
{
	cin>>n;
	t=log2(n)+1;
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	 } 
	cin>>u>>v;
	bfs();
	lca=LCA(u,v);
	for(int i=1;i<=n;i++)
	{
		ans=max(ans,d[i]);//最深的深度 
	}
	cout<<ans<<endl;
	ans=0;
	for(int i=1;i<=t;i++)//遍历每层节点个数 
	{
		ans=max(ans,dep[i]);
	}
	cout<<ans<<endl;
	int len=(d[u]-d[lca])*2+(d[v]-d[lca]);//此题规定的两点距离公式
	cout<<len<<endl;
	return 0; 
}

LCA详解 - TEoS - 博客园 (cnblogs.com)

例题P3884 [JLOI2009] 二叉树问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
const int N=6e5;
int n,m,s,t,tot=0,f[N][20],d[N],ver[2*N],Next[2*N],head[N];
queue<int> q;
void add(int x,int y)
{
    ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
}//邻接表存边操作。由于只求LCA时不关心边权,因此可以不存边权
void bfs()
{
    q.push(s);
    d[s]=1;//将根节点入队并标记
    while(q.size())
    {
        int x=q.front();q.pop();//取出队头
        for(int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if(d[y])
                continue;
            d[y]=d[x]+1;
            f[y][0]=x;//初始化,因为y的父亲节点就是x
            for(int j=1;j<=t;j++)
                f[y][j]=f[f[y][j-1]][j-1];//递推f数组
            q.push(y);
        }
    }
}
int lca(int x,int y)
{
    if(d[x]>d[y])
        swap(x,y);
    for(int i=t;i>=0;i--)
        if(d[f[y][i]]>=d[x])
            y=f[y][i];//尝试上移y
    if(x==y)
        return x;//若相同说明找到了LCA
    for(int i=t;i>=0;i--)
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i],y=f[y][i];
        }//尝试上移x、y并保持它们不相遇
    return f[x][0];//当前节点的父节点即为LCA
}
int main()
{
    cin>>n>>m>>s;
    t=log2(n)+1;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    bfs();
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
    return 0;
}

### LCA(最近公共祖先)问题的模板代码与算法实现 对于解决LCA问题,倍增法是一种高效的方法。该方法通过预处理节点之间的关系来加速查询过程。 #### 倍增法求解LCA的核心思想 核心在于预先计算每个节点向上跳转$2^k$步后的父节点位置,并记录这些跳跃路径上的最值或和等信息以便后续快速查询[^1]。 #### 预处理阶段 在预处理过程中,构建稀疏表用于存储从任意一点出发经过若干次二进制幂次数目的上移操作所能到达的位置以及对应的最大最小边权或其他属性值。 ```cpp const int MAXN = 5e4 + 5; int dep[MAXN], fa[MAXN][20]; //dep[i]表示i结点到根的距离,fa[i][j]表示i结点往上第2^j个祖先是谁 vector<int> G[MAXN]; void dfs(int u,int father){ fa[u][0]=father; for(auto v : G[u]){ if(v==father) continue; dep[v]=dep[u]+1; dfs(v,u); } } //初始化f数组 for(int j=1;(1<<j)<=n;j++) for(int i=1;i<=n;i++) if(fa[i][j-1]!=-1) fa[i][j]=fa[fa[i][j-1]][j-1]; ``` #### 查询阶段 当需要找到两个给定节点u,v之间距离最近的那个共同祖先时,则可以通过比较两者深度并调整至相同层次再逐步回溯直至相遇于某一层级完成定位工作。 ```cpp int lca(int u,int v){ if(dep[u]<dep[v]) swap(u,v); //保证u更深或者一样深 for(int i=18;i>=0;i--) //先让较深的一个回到跟另一个同一层 if((dep[u]-dep[v])&(1<<i)) u=fa[u][i]; if(u==v) return u; //此时在同一层了 如果相等就返回 for(int i=18;i>=0;i--) if(fa[u][i]!=fa[v][i]){ u=fa[u][i]; v=fa[v][i]; } //一起往上面走直到第一次不一致的地方停下 return fa[u][0]; //最后一步走到lca处 } ``` 上述C++代码实现了基于DFS遍历树结构来进行倍增法预处理,并提供了`lca()`函数用来在线询问两节点间最低公共祖先的具体编号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值