LCA 倍增+O(1)

LCA

倍增

时间复杂度为 O(logN) O ( l o g N ) 的在线算法

Dep[v] D e p [ v ] 记录节点 v v 的深度,Fa[v][k]记录 v v 向上第2k个祖先的编号

如果是一个森林,首先要判断两个点在不在同一棵树里,有两种方法:①把所有森林的根节点连到一个超级源点上,如果两个点的LCA是这个源点,那么这两个点不在一颗树里;②并查集getFather()一下。不过第二种方法更好,因为代码更好写。

O(1)LCA

询问 x x y的LCA

  • 求出一棵树的欧拉序列

  • 找出 x x y最早在欧拉序列中出现的位置 a a b

  • 求出 a a b中欧拉序最小的点(ST表预处理)

代码

不带权
#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define N 10050
using namespace std;
ll f[N][20],End[N],Next[N],Last[N],tot,Dep[N];
bool used[N];
void Ins(ll x,ll y){End[++tot]=y,Next[tot]=Last[x],Last[x]=tot;}
void DFS(ll x){
    Dep[x]=Dep[f[x][0]]+1;
    ll s=ceil(log2(Dep[x]));
    for(ll i=1;i<=s;i++)f[x][i]=f[f[x][i-1]][i-1];
    for(ll i=Last[x];i;i=Next[i])DFS(End[i]);
}
ll Find(ll x,ll y){//Dep[x]<Dep[y]
    if(Dep[x]>Dep[y])swap(x,y);
    ll d=Dep[y]-Dep[x],s=ceil(log2(Dep[y]));
    for(ll i=0;i<=s;i++)
        if(d&(1<<i))y=f[y][i];
    if(x==y)return x;
    for(ll i=s;i>=0;i--)
        if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main(){
    ll n,m;scanf("%lld",&n);
    for(ll i=1;i<n;i++){
        ll x,y;scanf("%lld%lld",&x,&y);
        used[y]=true,f[y][0]=x,Ins(x,y);
    }
    for(ll i=1;i<=n;i++)
        if(!used[i]){DFS(i);break;}//以i为根 建树+预处理
    scanf("%lld",&m);
    for(ll i=1;i<=m;i++){
        ll a,b;scanf("%lld%lld",&a,&b);
        printf("%lld\n",Find(a,b));
    }
    return 0;
}
带权
#include<iostream>
#include<cstdio>
#include<cmath>
#define N 4000
using namespace std;
int End[N],Len[N],Next[N],Last[N],dis[N][N],f[N][N],D[N],tot;
bool used[N];
void Ins(int x,int y,int len){
    End[++tot]=y;
    Len[tot]=len;
    Next[tot]=Last[x];
    Last[x]=tot;
}
void DFS(int fa){
    used[fa]=true;
    D[fa]=D[f[fa][0]]+1;
    int s=ceil(log2(D[fa]));
    for(int i=1;i<=s;i++){
        f[fa][i]=f[f[fa][i-1]][i-1];
        dis[fa][i]=dis[f[fa][i-1]][i-1]+dis[fa][i-1];
    }
    for(int i=Last[fa];i;i=Next[i])
        if(!used[End[i]]){
            f[End[i]][0]=fa,dis[End[i]][0]=Len[i];
            DFS(End[i]);
        }
}
int Find(int x,int y){//D[x]>D[y]
    int ans=0;
    if(D[x]<D[y])swap(x,y);
    int d=D[x]-D[y],s=ceil(log2(D[x]));
    for(int i=0;i<=s;i++)
        if(d&(1<<i))ans+=dis[x][i],x=f[x][i];
    if(x==y)return ans;
    for(int i=s;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            ans+=dis[x][i]+dis[y][i];
            x=f[x][i],y=f[y][i];
        }
    return ans+dis[x][0]+dis[y][0];
}
int main(){
    int n,q;scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++){
        int a,b,l;scanf("%d%d%d",&a,&b,&l);
        f[b][0]=a,dis[b][0]=l;
        Ins(a,b,l),Ins(b,a,l);
    }
    DFS(1);

    for(int i=1;i<=q;i++){
        int x,y;scanf("%d%d",&x,&y);
        printf("%d\n",Find(x,y));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值