倍增法在线求LCA(详解)

先贴上学习的文章:

洛谷P3379题解1

倍增讲解

在用Tarjan算法求LCA时会出现超时的现象,因为是一层一层的往上跳,于是就有了用倍增法求LCA,是2^{^{k}}层地往上跳。复杂度:O(nlogn)

1.倍增法思想:

按2的倍数跳,不过是从大到小跳,32,16,8,4,2,1。原因是从小到大跳会出现“悔棋”的现象:

从小到大:5!=1+2+4

从大到小:5=4+1

5<32 (不跳)

5<16(不跳)

5<8(不跳)

5>=4(向上跳4格到1)

1<2(不跳)

1>=1(向上跳1格到0)

2.算法解析:

step1:预处理

目标:

  1. 在dfs的时候求出每个节点的深度。
  2. 求出fa[x][i]:=x节点向上跳2^{^{i}}层后在哪个节点上,这里要注意,节点可能向上跳地太猛了,以至于都跳出了根节点,这种对我们做题没有意义,所以只要求出向上跳的最大层数(也就是节点深度dep[x])就可以啦~

可知:fa[x][0]=x的父节点

          fa[x][1]=fa[x的父节点][0]=fa[fa[x][0]][0]

          fa[x][i]=fa[fa[x][i-1]][i-1]

原因是:比如向上跳2^{^{3}}=8层:

当前i节点向上跳8/2=4=2^{^{2}}层,到达点j

再由j节点向上跳8/2=4=2^{^{2}}层,到达目的节点。(一共跳了2^{^{3}}层)

void dfs(int now,int pre){
    dep[now]=dep[pre]+1;
    vis[now]=1;
    fa[now][0]=pre;
    for(int i=1;(1<<i)<=dep[now];i++){//向上跳的层数(1<<i)<=节点的深度dep[now]
        fa[now][i]=fa[fa[now][i-1]][i-1];
    }
    for(int i=head[now];i!=-1;i=edge[i].nex){
        int v=edge[i].to;
        if(v==pre)continue;
        dis[v]=dis[now]+edge[i].cost;
        dfs(v,now);
    }
}

step2:常数优化

节点n向上跳的范围是[1,log2(n)]

for(int i=1;i<=n;i++){//求出log2(i)+1的值
    lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}

可以模拟一下:

lg[1]=0+(1==1)=1

lg[2]=1+(1==1)=2

lg[3]=2+(4==3)=2

lg[4]=2+(4==4)=3

这里为什么用递推公式求log2(i)+1,而不用cmath里的函数求log2(i),原因是:

log是以e为底数, log10是以10为底数

求log2(i),则:log(i)/log(2)速度慢,不如求出log2(i)+1的值存在lg[i]数组里,需要的时候用lg[i]-1

step3:倍增LCA

有了上两步的铺垫,现在就好求多了。

求倍增LCA的思想是:

  1. 先把两个点提到统一高度,再一起跳。
  2. 向上跳也是有原则的:要跳到LCA的下面一层。然后该层对应节点的父节点就是要求的LCA

比如节点7和9,9先跳到8,此时和7是同一层,若是跳地太多到1,就会错认为1是LCA

正确的是7->4   9->8->5

fa[4][0]或fa[5][0]=3就是答案


int lca(int x,int y){
    if(dep[x]<dep[y]){
        swap(x,y);
    }
    while(dep[x]>dep[y]){
        x=fa[x][lg[dep[x]-dep[y]]-1];
    }
    if(x==y)return x;
    for(int k=lg[dep[x]];k>=0;k--){
        if(fa[x][k]!=fa[y][k]){
            x=fa[x][k];
            y=fa[y][k];
        }
    }
    return fa[x][0];
}

完结撒花✿✿ヽ(°▽°)ノ✿

上一道模板题:

hdu2874

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<cmath>
#include<set>
#include<map>
using namespace std;
#define ll long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef pair<int,int>P;
const int INF=0x3f3f3f3f;
const int N=100015;

int head[N],cnt,dis[N],pre[N];
int dep[N],fa[N][22],lg[N];
int vis[N];
struct A{
    int to,nex,cost;
}edge[N];

int f(int x){
    if(pre[x]==x)return pre[x];
    pre[x]=f(pre[x]);
    return pre[x];
}

void link(int a,int b){
    int r1=f(a),r2=f(b);
    if(r1!=r2){
        pre[r2]=r1;
    }
}

void add(int from,int to,int cost){
    edge[cnt].to=to;
    edge[cnt].cost=cost;
    edge[cnt].nex=head[from];
    head[from]=cnt++;
}

void dfs(int now,int pre){
    dep[now]=dep[pre]+1;
    vis[now]=1;
    fa[now][0]=pre;
    for(int i=1;(1<<i)<=dep[now];i++){
        fa[now][i]=fa[fa[now][i-1]][i-1];
    }
    for(int i=head[now];i!=-1;i=edge[i].nex){
        int v=edge[i].to;
        if(v==pre)continue;
        dis[v]=dis[now]+edge[i].cost;
        dfs(v,now);
    }
}

int lca(int x,int y){
    if(dep[x]<dep[y]){
        swap(x,y);
    }
    while(dep[x]>dep[y]){
        x=fa[x][lg[dep[x]-dep[y]]-1];
    }
    if(x==y)return x;
    for(int k=lg[dep[x]];k>=0;k--){
        if(fa[x][k]!=fa[y][k]){
            x=fa[x][k];
            y=fa[y][k];
        }
    }
    return fa[x][0];
}

void init(int n){
    for(int i=1;i<=n;i++)pre[i]=i;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(dep,0,sizeof(dep));
    memset(dis,0,sizeof(dis));
    cnt=0;
    memset(fa,0,sizeof(fa));
}

int main(){
    int n,m,k,s,t;
    for(int i=1;i<=1000;i++){
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    }
    while(scanf("%d%d%d",&n,&m,&k)!=EOF){
        init(n);
        int a,b,c;
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&a,&b,&c);
            link(a,b);
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                dfs(i,0);
            }
        }
        for(int i=1;i<=k;i++){
            scanf("%d%d",&a,&b);
            if(f(a)!=f(b)){
                printf("Not connected\n");
            }
            else printf("%d\n",dis[a]+dis[b]-2*dis[lca(a,b)]);
        }
    }
}

 

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值