学习LCA 基于rmq的lca 代码注释

蒟蒻一开始一直把这个当倍增法,感谢qq_39873602的指正。
程序逻辑:初始化->加边->DFS->RMQ->LCA。
首先是DFS(记录信息):

坐标 :1 2 3 4 5 6 7 8 9 10 11 12
dfs顺序:1 2 4 2 5 2 1 3 6 3 7 8 (每个坐标对应的结点的编号)
first pos: 1 2 3 2 5 2 1 8 9 8 11 12(每个结点第一次出现的坐标)
深度: 1 2 3 2 3 2 1 2 3 2 3 4
tot:深搜的坐标
id[]:每个坐标对应的结点的编号,
first[]:每个节点第一次出现的坐标,
deep[]:每个坐标所在结点的深度。

void dfs(int u,int fa,int dep){
    id[++tot]=u;//tot坐标对应结点为u 
    first[u]=tot;//第一次出现u结点是在tot坐标 
    deep[tot]=dep;//更新tot坐标的深度
    for(int k=head[u];k!=-1;k=e[k].next){
        int v=e[k].v;
        if(v==fa) continue;
        dfs(v,u,dep+1);
        //表示dfs还要回溯到父亲结点
        deep[++tot]=dep;
        id[tot]=u;
    } 
}

RMQ:
比如要找6和8的LCA,看dfs顺序6 3 7 8,3是深度最小的,那么3就是6和8的LCA。现在就是求区间的最小深度了,想到可以用RMQ算法。

//dp[i][j]:表示区间[i,i+(1<<j)-1]的最小深度的结点编号。
void getst(int n){//RMQ前的初始化 
    for(int i=1;i<=n;i++){
        //坐标i:[i,i]区间的最小深度的结点编号就是i
        dp[i][0]=i;
    }
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            //ll和rr是左右两部分的最小深度的结点编号
            int ll=dp[i][j-1],rr=dp[i+(1<<(j-1))][j-1];
            dp[i][j]=deep[ll]<deep[rr]?ll:rr;//更新最小深度编号
        }
    } 
} 

int rmq(int l,int r){
    int k=(int)log(r-l+1)/log(2.0);
    int ll=dp[l][k],rr=dp[r-(1<<k)+1][k];
    return deep[ll]<deep[rr]?ll:rr;//返回最小深度的坐标 
} 

LCA:

int lca(int u,int v){
    u=first[u],v=first[v];//结点第一次出现坐标 
    if(u>v) swap(u,v);
    int res=rmq(u,v);//求最小深度的坐标
    return id[res];//返回对应的结点 
}

完整代码:

#include<set>
#include<map>
#include<stack>
#include<queue>
#include<vector>
#include<string>
#include<bitset>

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>

#include<iomanip>
#include<iostream>


#define debug cout<<"aaa"<<endl
#define d(a) cout<<a<<endl
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define lson l,mid,root<<1
#define rson mid+1,r,root<<1|1
#define MIN_INT (-2147483647-1)
#define MAX_INT 2147483647
#define MAX_LL 9223372036854775807i64
#define MIN_LL (-9223372036854775807i64-1)
using namespace std;

const int N = 100000 + 5;
const int mod = 1000000000 + 7;
const double eps = 1e-8;
int edgenum,head[N];//edgenum:边的数量 head:存兄弟结点 
int id[N<<1],deep[N<<1],first[N];//id:该坐标对应的结点编号 deep:深度 first:点编号第一次出现的坐标
int dp[N<<1][30];
int tot;//深搜坐标 

struct Edge{
    int v,next;
    Edge(int v=0,int next=0):v(v),next(next){}
}e[N<<1]; 

void init(){
    tot=0,edgenum=0;
    mem(head,-1);
}

void add(int u,int v){
    e[edgenum]=Edge(v,head[u]);
    head[u]=edgenum++;
}

void dfs(int u,int fa,int dep){
    id[++tot]=u;//tot坐标对应结点为u 
    first[u]=tot;//第一次出现u结点是在tot坐标 
    deep[tot]=dep;//更新tot坐标的深度
    for(int k=head[u];k!=-1;k=e[k].next){
        int v=e[k].v;
        if(v==fa) continue;
        dfs(v,u,dep+1);
        //下面两句表示dfs还要回溯到父亲结点 
        deep[++tot]=dep;
        id[tot]=u;
    } 
}

void getst(int n){//RMQ前的初始化 
    for(int i=1;i<=n;i++){
        dp[i][0]=i;
    }
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            int ll=dp[i][j-1],rr=dp[i+(1<<(j-1))][j-1];
            dp[i][j]=deep[ll]<deep[rr]?ll:rr;
        }
    } 
} 

int rmq(int l,int r){
    int k=(int)log(r-l+1)/log(2.0);
    int ll=dp[l][k],rr=dp[r-(1<<k)+1][k];
    return deep[ll]<deep[rr]?ll:rr;//返回最小深度的坐标 
} 

int lca(int u,int v){
    u=first[u],v=first[v];//结点第一次出现坐标 
    if(u>v) swap(u,v);
    int res=rmq(u,v);//求最小深度的坐标
    return id[res];//返回对应的结点 
}

int main(){
    int n,q,u,v,LCA;
    init();
    scanf("%d%d",&n,&q);
    //加边 
    for(int i=2;i<=n;i++){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dfs(1,1,0);
    getst(tot);
    while(q--){
        scanf("%d%d",&u,&v);
        LCA=lca(u,v);
        printf("%d和%d的LCA:%d\n",u,v,LCA); 
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值