浅谈倍增法求LCA

1 篇文章 0 订阅
1 篇文章 0 订阅

在有根树T中,两个节点a、b的最近公共祖先(Lowest Common Ancestors,LCA)是某个点p,使得p同时是a、b的祖先,而且p离根最远。

在树上求最短路时,相较于其他的最短路算法,一些快速求LCA的算法有更高的效率。

这里我们介绍以倍增法为基础的LCA算法。(又称“跳表法”);
具体过程是使用dfs建树,维护具体信息,再用倍增法巧妙求得LCA,便于理解记忆

在dfs的过程中维护三个数组:
depth[i],表示i点在树中的深度;
grand[x][i],表示x的第2^i个祖先的节点编号;
gdis[x][i],表示x到它2^i祖先的距离。

初始化:

const int maxn = 40010;
const int maxlog = 20;//maxlog = log2(maxn)
int grand[maxn][maxlog],gdis[maxn][maxlog];
int depth[maxn];
int n,m,q,s;

void Init(){
    //n为节点个数 
    s = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先
    depth[0]=-1;//根结点的祖先不存在,用-1表示 
    dfs(1);//以1为根节点建树 
} 

这是dfs过程:
每次dfs先更新x结点的grand和gdis,然后搜索与x相连的结点

void dfs(int x){//dfs建树 
    for(int  i = 1; i <= s; i++){
        grand[x][i] = grand[grand[x][i-1]][i-1];//x的2^i祖先是它2^i-1祖先的2^i-1祖先 
        gdis[x][i] = gdis[x][i-1] + gdis[grand[x][i-1]][i-1];//x到它2^i祖先的距离 = x到它2^i-1祖先的距离 + x的2^i-1祖先到它2^i-1祖先距离
        if(!grand[x][i]) break; //到子树的根了 
    }
    for(int  i = 0; i < G[x].size(); i++){
        Edge & e = edges[G[x][i]]; 
        if(e.to != grand[x][0]){//与x相连的结点,不是它的父亲,就是他的儿子
            depth[e.to] = depth[x] +1;//维护深度 
            grand[e.to][0] = x;//维护父子关系 
            dis[e.to][0] = e.dist;//维护距离 
            dfs(e.to);
        }
    }
}

这是求LCA过程:
将a,b两个结点从它们原本的深度向上“跳”,直到它们到达它们的LCA,一边跳一边加上边权
采取先让深度大的点跳,再让两个点一起跳的方式

int LCA(int a, int b){
    if(depth[a] > depth[b]) swap(a, b);//保证a在b上面,便于计算
    int ans = 0;
    for(int i = s; i >= 0; i--) //类似于二进制拆分,从大到小尝试
        if(depth[a] < depth[b] && depth[grand[b][i]] >= depth[a])//a在b下面且b向上跳后不会到a上面
            ans +=gdis[b][i], b =grand[b][i];//先把深度较大的b往上跳 
    for(int i = s; i >= 0; i--)
        if(grand[a][i] != grand[b][i])//a,b的2^i祖先不一样 => 没跳到同样深度位置,接着跳
            ans += gdis[a][i], ans += gdis[b][i], a = grand[a][i], b = grand[b][i];//一起往上跳 
    if(a != b) ans += gdis[a][0], ans += gdis[b][0];//没跳到一个地方,那么再往上一个结点就是它们的LCA 
    return ans;
}

妙不可言的模板题链接:
[USACO FEB04]距离咨询

注:存图方式为刘汝佳《入门经典》介绍的vector邻接表,代码如下:

struct Edge{ 
    int from,to,dist;
    Edge(int u,int v,int w):from(u),to(v),dist(w){}
};
const int maxn = 40010;//节点数
vector<Edge> edges;//边的具体信息
vector<int> G[maxn];//边的编号

void addEdge(int u, int v, int w){//加入一条边(无向图)
    edges.push_back(Edge(u,v,w));
    edges.push_back(Edge(v,u,w));
    int size = edges.size();
    G[u].push_back(size-2);
    G[v].push_back(size-1);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值