【模板】LCA

(十一集训前最后的挣扎)

先介绍LCA是啥吧。。

LCA:Lowest Common Ancestors(最近公共祖先)

用来求树上任意两点的最近相同父亲节点,有各种不同的方法,这里先介绍树上倍增求LCA(另一种我不会。。)

先看一道题:(RP++)

这是翻译:

先看看朴素算法

先依次向上查找x的祖先,存入xa数组,再依次向上查找y的祖先,与xa数组中的值比较,第一个相同的就是x,y的最近公共祖先。

每一次查找的时间复杂度是O(n)

(好慢。。)

树上倍增就是将查找的次数减少,每一次都尽可能多的往上走,大大减少查找次数。

这里倍增的倍就是指2的多少次方,但是倍增总要有个上限,对于有n层的树,上限就是log2n

流程图(摘自老师的PPT):

要怎么用代码实现呢。。

先看流程图:

出现了一个令我迷茫的东西----位运算符“&”

这个东西应该怎么用呢?

比如:1010 0011& 0000 1111,结果为0000 0011。也就是与上0相当于把那位数清0,与上1相当于把那位保留。(摘自百度知道)

(有点像快速幂。。)

但是只用单纯的树上倍增真的可以解决这道题吗?

当x,y不在同一层的时候就会有一点难处理。

这时我们可以先让在更深层的x或y先跳到与另一个节点同一层,再利用树上倍增求LCA。

大概思路就是这样。

看看代码?(想看建树的话请看SPFA

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn = 500005;
const int maxe = 1000005;
int n,m,root;

struct line{
    int from,to;
    line(){}//空构造函数 line p;
    line(int A,int B){
        //构造函数 line L=line(1,2);
        from=A;to=B;
    }
};
line edge[maxe];
int last[maxn],_next[maxe],e;
//last[x]表示以x为起点的最后一条边(的编号)
//_next[i]表示与第i条边起点相同的上一条边(的编号)
void add_edge(int x,int y){
    edge[++e]=line(x,y);
    _next[e]=last[x];
    last[x]=e;
}
int Fa[maxn][35],Dep[maxn];
void dfs(int x,int fa){
    int i,k,y;
    Fa[x][0]=fa;
    Dep[x]=Dep[Fa[x][0]]+1;                       //记录当前节点的深度
    k=ceil(log(Dep[x])/log(2));                      //x往上倍增的上限
    for(i=1;i<=k;i++)Fa[x][i]=Fa[Fa[x][i-1]][i-1];  //倍增计算祖先
    for(int i=last[x];i;i=_next[i]){
        int v=edge[i].to;
        if(v!=fa)dfs(v,x);
    }
}
int LCA(int x,int y){
    int i,k,s;
    s=ceil(log(n)/log(2));                 //该树倍增最大可能的上限
    if(Dep[x]<Dep[y])swap(x,y);      //交换x和y的值
    /////////////x往上走k层,让x与y处于同一层 //////////
    k=Dep[x]-Dep[y];
    for(i=0;i<=s;i++)
        if(k&(1<<i))x=Fa[x][i];
    if(x==y)return x;                     //x==y时,x就是最近公共祖先
    ///////////////////////////////////////////////////
    s=ceil(log(Dep[x])/log(2));           //计算向上倍增的上限
    for(i=s;i>=0;i--)
        if(Fa[x][i]!=Fa[y][i]){ x=Fa[x][i]; y=Fa[y][i]; }
    return Fa[x][0];
}

int main(){
    int i,j,k;
    cin>>n>>m>>root;
    for(i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add_edge(x,y);
        add_edge(y,x);
    }
    dfs(root,0);
    for(i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }

} 

关于核心代码的循环过程:

为什么有If(fa[u][i]!=fa[v][i]) { u=fa[u][i],v=fa[v][i]; } 这一句呢?不是当他们相等时就可以结束了吗?

原理:当fa[u][i]==fa[v][i]时,所求的节点不一定是最近的公共祖先。

(RP++!)

原文地址:https://www.cnblogs.com/Daz-Os0619/p/11469720.html

时间: 2024-10-13 17:19:48

【模板】LCA的相关文章

算法模板——LCA(最近公共祖先)

实现的功能如下——在一个N个点的无环图中,共有N-1条边,M个访问中每次询问两个点的距离 原理——既然N个点,N-1条边,则说明这是一棵树,而且联通.所以以1为根节点DFS建树,然后通过求两点的LCA的方式,先求得最近公共祖先,然后再通过深度来求出两点距离 1 type 2 point=^node; 3 node=record 4 g:longint; 5 next:point; 6 end; 7 const 8 maxn=100500; 9 maxm=trunc(ln(maxn)/ln(2))

[模板]LCA的倍增求法解析

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 5 5 4 3 1 2 4 5

[模板]LCA

洛谷P3379 注意:不能与LCA搞混(打久了就会发现两个还是有很大区别的) 位运算一定要加括号! for循环从0到logn还是从logn到0看当前的状态更适合哪种 第53行预处理一定要注意!(因为没有下标为-1的数组) 第34行也要注意如何判断当前是否跳点(不需要麻烦的位运算,因为如果能跳,dep[y]自然就会变,如果不跳,dep[y]又不变,每次与(dep[y]-dep[x])进行比较,不影响dep[x]与dep[y]的值:) 1 #include<bits/stdc++.h> 2 usi

ACM模板——LCA

1 #include <bits/stdc++.h> 2 #define _for(i,a,b) for(int i = (a);i < b;i ++) 3 #define _rep(i,a,b) for(int i = (a);i > b;i --) 4 #define INF 0x3f3f3f3f 5 #define pb push_back 6 #define maxn 500003 7 typedef long long ll; 8 using namespace std;

[模板]LCA全集

如题: LCA(倍增): 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 int n,m,s,tot=0; 9 const int N=500005,M=1000005; 10 int head[N]

POJ 1330 Nearest Common Ancestors (最近公共祖先LCA + 详解博客)

LCA问题的tarjan解法模板 LCA问题 详细 1.二叉搜索树上找两个节点LCA 1 public int query(Node t, Node u, Node v) { 2 int left = u.value; 3 int right = v.value; 4 5 //二叉查找树内,如果左结点大于右结点,不对,交换 6 if (left > right) { 7 int temp = left; 8 left = right; 9 right = temp; 10 } 11 12 whi

图论模板

图论模板 LCA 离线 tarjan //可使用快读 #include <iostream> #include <cstdio> using namespace std; const int MAXN=500050; int po[MAXN],qs[MAXN],fa[MAXN],nume,numq,n,m,root,ans[MAXN]; bool f[MAXN]; struct edge{ int to,nxt; }e[2*MAXN]; struct ques{ int to,nx

HDU 2586(LCA欧拉序和st表)

什么是欧拉序,可以去这个大佬的博客(https://www.cnblogs.com/stxy-ferryman/p/7741970.html)巨详细 因为欧拉序中的两点之间,就是两点遍历的过程,所以只要找遍历过程中对应的最小的深度就行了,这里用st表存,first存第一个u出现的地方,用value存欧拉序,同时用depth存对应深度 模板 1 struct node{ 2 int v,next,dist; 3 }a[maxn<<1]; 4 int n,m,tot,len; 5 int st[m

【洛谷P3379】【模板】最近公共祖先(LCA)

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 5 5 4 3 1 2 4 5

求lca(模板)

洛谷——P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样