文章标题 RMQ转LCA算法、基于倍增的方法求LACA

5 篇文章 0 订阅
3 篇文章 0 订阅

LCA

以RMQ来实现

LCA(最近公共祖先)
用RMQ来实现,首先把树链哈希到链上。在链上用RMQ求最小值。
哈希这个过程可以用深搜,然后记录每个人节点出现的顺序,比如
对于下面这个图
这里写图片描述
通过深搜可以得到
这里写图片描述
然后我们用一个first数组来表示每个节点第一次出现的位置(即数组下标),比如我们找D和G的LCA,D第一次出现的位置为3,G第一次出现的位置为8,然后找他们的LCA就是找[3,8]这个区间的深度的最小值了。而这个树的节点的深度的区间最小值就可以用RMQ来实现了。
其中图的建立用邻接表

struct Edge{
    int v,w;
    Edge(int v_=0,int w_=0){
        v=v_;
        w=w_;
    }
};

vector<Edge>G[maxn];

然后深搜过程为

void dfs(int u,int idx){
    vis[u]=true;//标记是否访问过
    point[++cnt]=u;//point表示节点的标号
    first[u]=cnt;//first表示u这个节点第一次出现的位置
    dep[cnt]=idx;//深度
    for (int i=0;i<G[u].size();i++){
        int v=G[u][i].v;
        int w=G[u][i].w;
        if (!vis[v]){
            dis[v]=dis[u]+w;//dis[v]表示树根到达v的距离
            dfs(v,idx+1);
            point[++cnt]=u;
            dep[cnt]=idx;
        } 
    }
}

然后就是用RMQ预处理出任意两个位置之间的最小值(深度)

void RMQ_init(int n){
    for (int i=1;i<=n;i++)dp[i][0]=i;
    for (int k=1;(1<<k)<=n;k++){
        for (int i=1;i+(1<<k)-1<=n;i++){
            int a=dp[i][k-1];
            int b=dp[i+(1<<(k-1))][k-1];
            if (dep[a]>dep[b])dp[i][k]=b;
            else dp[i][k]=a;
        }
    }
} 

int RMQ(int l,int r){
    int k=0;
    while ((1<<(k+1))<=r-l+1)k++;
    int a=dp[l][k];
    int b=dp[r-(1<<k)+1][k];//保存的是编号
    return dep[a]>dep[b]?b:a;
}

最后就是O(1)来得到LCA了

int LCA(int u,int v){
    int x=first[u],y=first[v];
    if (x>y)swap(x,y);
    int ans=RMQ(x,y);
    return point[ans];//返回编号所对应的节点。
}

基于倍增的方式

const int LOG = 20;//根据点数来取,取LOG >=logn就行了 20已经可以处理1e6以下的数据了

int par[N][LOG],dep[N];

void dfs(int u,int fa,int depth){
    dep[u]=depth;
    if(u==1){   //对于根来说,往上跳无论几下都是他自己。我习惯把1定为根所以写 u == 1
       for(int i=0;i<LOG;i++) par[u][i]=u;
    }else{
        par[u][0]=fa;   //往上跳2^0也就是1步显然是他的父亲
        for(int i=1;i<LOG;i++) par[u][i]=par[par[u][i-1]][i-1];//类似RMQ的ST表的构造方法
    }
    for(int i=first[u];i!=-1;i=edge[i].next){
        int v=edge[i].e;
        if(v==fa) continue;
        dfs(v,u,depth+1);
    }
}

int up(int x,int step){ //返回x往上跳step步后的位置
    for(int i=0;i<LOG;i++) if(step&(1<<i))
        x=par[x][i];
    return x;
}

int get_lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);    //使得u为深度更大的点
    u=up(u,dep[u]-dep[v]);      //u往上跳dep[u] - dep[v]步使得u和v同等深度
    if(u==v) return u;      //u和v重合,LCA就是v
    for(int i=LOG-1;i>=0;i--){  //同时跳
        if(par[u][i]!=par[v][i]){
            u=par[u][i];
            v=par[v][i];
        }
    }
    return par[u][0];
}

下面是一个LCA的板题,HDU 2586 传送门
代码:
RMQ转LCA

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <math.h>
#include <vector>
using namespace std;
typedef long long ll;

const int mod=1e9+7;
const int maxn=4*1e4+10;

int n,m;

struct Edge{
    int v,w;
    Edge(int v_=0,int w_=0){
        v=v_;
        w=w_;
    }
};

vector<Edge>G[maxn]; 
int dis[maxn];//dis[i]表示节点i到树根的距离 
int dp[maxn*2][20];
int first[maxn];//first[i]表示i这个节点第一次出现的标号 
int dep[maxn*2];//深度 
int point[maxn*2];//point[i]表示标号为i所对应的节点 
int vis[maxn];//标记节点是否被访问过了 
int cnt;//遍历是节点的数目 

void dfs(int u,int idx){//深搜 
    vis[u]=true;
    point[++cnt]=u;
    first[u]=cnt;
    dep[cnt]=idx;
    for (int i=0;i<G[u].size();i++){
        int v=G[u][i].v;
        int w=G[u][i].w;
        if (!vis[v]){
            dis[v]=dis[u]+w;
            dfs(v,idx+1);
            point[++cnt]=u;
            dep[cnt]=idx;
        } 
    }
}
void RMQ_init(int n){//预处理 
    for (int i=1;i<=n;i++)dp[i][0]=i;
    for (int k=1;(1<<k)<=n;k++){
        for (int i=1;i+(1<<k)-1<=n;i++){
            int a=dp[i][k-1];
            int b=dp[i+(1<<(k-1))][k-1];
            if (dep[a]>dep[b])dp[i][k]=b;
            else dp[i][k]=a;
        }
    }
} 

int RMQ(int l,int r){//求最小值 
    int k=0;
    while ((1<<(k+1))<=r-l+1)k++;
    int a=dp[l][k];
    int b=dp[r-(1<<k)+1][k];
    return dep[a]>dep[b]?b:a;
}
int LCA(int u,int v){//求LCA 
    int x=first[u],y=first[v];
    if (x>y)swap(x,y);
    int ans=RMQ(x,y);
    return point[ans];
}

int main()
{
    int T;
    scanf ("%d",&T);
    while (T--){
        memset (vis,0,sizeof (vis));
        scanf ("%d%d",&n,&m);
        for (int i=0;i<=n;i++)G[i].clear();
        int u,v,w;
        for (int i=0;i<n-1;i++){
            scanf ("%d%d%d",&u,&v,&w);
            G[u].push_back(Edge(v,w));
            G[v].push_back(Edge(u,w));
        }
        dis[1]=0;cnt=0;
        dfs(1,1);
        RMQ_init(2*n-1);
        while (m--){
            scanf ("%d%d",&u,&v);
            int lca=LCA(u,v);
            int ans=dis[u]+dis[v]-2*dis[lca];
            printf ("%d\n",ans);
        }
    }
    return 0;
}

倍增的方式

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <math.h>
#include <vector>
using namespace std;
typedef long long ll;

const int mod=1e9+7;
const int maxn=40000+10;
const int LOG=20;

struct Edge{
    int v,w;
};

vector<Edge>G[maxn];
int par[maxn][LOG];
int dep[maxn];
int dis[maxn];
void dfs(int u,int fa,int depth){
    dep[u]=depth;
    if (u==1)for (int i=0;i<LOG;i++)par[u][i]=u;
    else {
        par[u][0]=fa;
        for (int i=1;i<LOG;i++)par[u][i]=par[par[u][i-1]][i-1];
    }
    for (int i=0;i<G[u].size();i++){
        int v=G[u][i].v;
        if (v==fa)continue;
        dis[v]=dis[u]+G[u][i].w;
        dfs(v,u,depth+1);
    }
}
int up(int x,int step){
    for (int i=0;i<LOG;i++)if (step&(1<<i)){
        x=par[x][i];
    }
    return x;
}

int get_lca(int u,int v){
    if (dep[v]>dep[u])swap(u,v);
    u=up(u,dep[u]-dep[v]); 
    if (u==v)return u;
    for (int i=LOG-1;i>=0;i--){
        if (par[u][i]!=par[v][i]){
            u=par[u][i];
            v=par[v][i];
        }
    }
    return par[u][0];
}

int main()
{
    int T;
    scanf ("%d",&T);
    int n,m;
    while (T--){
        scanf ("%d%d",&n,&m);
        for (int i=0;i<=n;i++)G[i].clear();
        int u,v,w;
        for (int i=0;i<n-1;i++){
            scanf ("%d%d%d",&u,&v,&w);
            G[u].push_back(Edge{v,w});
            G[v].push_back(Edge{u,w});
        }
        dfs(1,-1,1);
        while (m--){
            scanf ("%d%d",&u,&v);
            int lca=get_lca(u,v);
            printf ("%d\n",dis[u]+dis[v]-2*dis[lca]);
        } 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值