HELLO WORLD--一起加油|

kingwzun

园龄:3年6个月粉丝:111关注:0

📂树论
🔖LCA
2022-04-05 10:12阅读: 61评论: 0推荐: 0

最近公共祖先LCA 模板

前置知识

  1. 链式前向星

LCA问题简述

最近公共祖先简称 LCA (Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。
为了方便,我们记某点集S=v1,v2,...,vn的最近公共祖先为 LCA(v1,v2,...,vn)LCA(S)
自己是自己的祖先

性质

  1. 对于求多个点的最近公共祖先,我们并不真的要对所有点两两都求一次。我们只用取这些点中dfs序最小和最大的两个点来求最近公共祖先就行。
    题目:https://www.cnblogs.com/kingwz/p/16518746.html

向上标记法

一般不用

  • 从x向上走到根节点, 并标记路径上经过的点
  • 从y向上走到根节点, 当遇到第一个被标记的点就找到了LCA(x, y)

倍增法

倍增的意思就是们不用每次向上爬一个,而是向上爬2^n个

具体分析看:原文

步骤: 每次向上爬2^n

  • [1] 先将两个点跳到同一层
  • [2] 让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层。

数据定义:

  • fa[i][j]表示从i开始,向上走2^j步所能走到的结点。0 <= j <= logn
  • dep[i]表示深度
    • 哨兵:如果从i开始跳2^j步会跳过根结点,那么fa[i][j] = 0。dep[0] = 0

预处理:

  1. 结点深度:
    深度的更新就是他爸爸的深度+1
  2. 结点2i级的祖先
    由于2(j1)+2(j1)=2j
    所以:
    i的2(j1)级祖先的2(j1)级祖先 就是i的2^j级祖先。
    故:
    fa[i][j]=fa[fa[i][j-1]][j-1]

复杂度:

  • 预处理 O(nlogn)
  • 查询 O(logn)

讲解代码

模板题洛谷p3379

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=500005;
vector <int> e[maxn];
int n,m,s,dep[maxn],fa[maxn][21];
int read() //快读
{
int ans=0,flag=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
flag=-1;
ch=getchar();
}
while(isdigit(ch))
{
ans=ans*10+ch-'0';
ch=getchar();
}
return ans*flag;
}
void dfs(int x,int father) //x为当前节点,father为他的爸爸
{
dep[x]=dep[father]+1; //x的深度是他父亲的深度+1
fa[x][0]=father; //2^0是1,x向上一个的祖先就是他爸爸
for(int i=1;(1<<i)<=dep[x];i++) //1<<i就是2^i,当2^i小于x的深度时,枚举他的祖先
{
fa[x][i]=fa[fa[x][i-1]][i-1]; //上面说过的fa数组的更新
}
for(int i=0;i<e[x].size();i++) //枚举与他相邻的边(我用的是vector存图)
{
if(e[x][i]!=father) //如果不是他爸爸
{
dfs(e[x][i],x); //继续dfs
}
}
return;
}
int lca(int u,int v) //u,v就相当于先讲的x和y
{
int temp; //temp是两个点的深度度差
if(dep[u]<dep[v]) //我们默认u的深度大一些,否则将u与v交换
swap(u,v);
temp=dep[u]-dep[v]; //计算深度差
for(int i=0;(1<<i)<=temp;i++) //将u的深度变得与v相同,还是使用倍增的思想
{
if((1<<i)&temp) //这个操作就相当于将深度差变成几个2^n的和,判断2^i是不是其中一个
{
u=fa[u][i]; //将u跳到他祖先的位置
}
}
if(u==v) //如果u刚好等于v,即他们已经变成了同一个点
{
return u; //返回这个点的值,就是LCA
}
for(int i=(int)(log(n)/log(2));i>=0;i--) //(int)(log(n)/log(2))就是n以内最大的2的次方,从最大的开始倍增
{
if(fa[u][i]!=fa[v][i]) //如果他们的爸爸不相同,即没有找到LCA
{
u=fa[u][i];
v=fa[v][i]; //一起倍增
}
}
return fa[u][0]; //返回他们的爸爸,即是LCA
}
int main()
{
n=read();
m=read();
s=read();
for(int i=1;i<=n-1;i++)
{
int x=read(),y=read();
e[x].push_back(y);
e[y].push_back(x); //vector存图
}
dfs(s,0); //预处理
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
int ans=lca(x,y);
printf("%d\n",ans);
}
return 0;
}

模板代码

模板题洛谷p3379

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=500005;
vector <int> g[maxn];
int n,m,s,dep[maxn],fa[maxn][21];
void dfs(int x,int f){
dep[x]=dep[f]+1;
fa[x][0]=f;
for(int i=1;(1<<i)<=dep[x];i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(int t:g[x]){
if(t!=f){
dfs(t,x);
}
}
return ;
}
int lca(int u,int v){
if(dep[v]>dep[u]) swap(v,u);
int temp=dep[u]-dep[v];
for(int i=0;(1<<i)<=temp;i++){
if((1<<i)&temp){
u=fa[u][i];
}
}
if(u==v) return u;
int mx=log(n)/log(2);
for(int i=mx;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i],v=fa[v][i];
}
}
return fa[u][0];
}
int main()
{
cin>>n>>m>>s;
for(int i=1;i<=n-1;i++)
{
int x,y;cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(s,0);
for(int i=0;i<m;i++)
{
int x,y;cin>>x>>y;
int ans=lca(x,y);
printf("%d\n",ans);
}
return 0;
}

Tarjan——离线求LCA O(n+m)

在深度优先遍历时,将所有点分成三大类:

  • [1] 已经遍历过,且回溯过的点
  • [2] 正在搜索的分支
  • [3] 还未搜索到的点

模板代码

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16101581.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(61)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起