lca倍增法与欧拉序

记录做题记录方便复习//落谷3379

一、倍增法

  倍增法关键在于两者先跳跃到同一深度之后同时向上跳,是暴力解法的优化

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
inline int read(){//快读
    int x=0,s=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')s=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*s;
}#define maxn 500005
int n,m,s;
int dep[maxn],f[maxn][20],lg[maxn];//存储深度;i向上跳跃2的y次方到达的位置;log值
struct node{
    int to,next;
}b[maxn<<1];
int head[maxn],cnt;
inline void add(int u,int v){//链式前向星存图
    b[++cnt].to=v;
    b[cnt].next=head[u];
    head[u]=cnt;
}
void dfs(int now,int fa){//当前节点;父亲节点
    f[now][0]=fa;//往上跳2的0次方即为其父亲节点
    dep[now]=dep[fa]+1;更新当前节点深度
    for(int i=1;i<=lg[dep[now]];++i)//更新当前节点跳跃2的i次方到达的点
        f[now][i]=f[f[now][i-1]][i-1];//当前节点向上跳跃2的i次方相当于向上跳跃2的2-1次方后再跳跃2的i-1次方
    for(int i=head[now];i;i=b[i].next)//当前节点向下深搜
        if(b[i].to!=fa) dfs(b[i].to,now);
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);//保证x深度大于y,便于操作
    while(dep[x]>dep[y])
        x=f[x][lg[dep[x]-dep[y]]];//先跳跃到同一深度
    if(x==y) return x;//深度相同为同一节点即为lca
    for(int k=lg[dep[x]];k>=0;k--)
        if(f[x][k]!=f[y][k])//从大到小同时向上跳跃到第一个不相同的节点
            x=f[x][k],y=f[y][k];
    return f[x][0];//该节点的父亲节点即为lca
}
int main(){
    n=read(),m=read(),s=read();
    int x,y;
    rep(i,1,n-1){
        x=read(),y=read();
        add(x,y);add(y,x);
    }
    rep(i,2,n){
        lg[i]=lg[i>>1]+1;//前期log处理
    }
    dfs(s,0);//从根节点往下深搜
    rep(i,1,n){
        x=read(),y=read();
        printf("%d\n",lca(x,y));
    }
    return 0;
}

二、欧拉序

基于欧拉序利用st表得到任意两节点的lca

欧拉序即为遍历树的过程记录当前节点出现的次数与当前的深度

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define pre(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
inline int read(){
    int x=0,s=1;
    char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')s=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*s;
}
#define maxn 500005
int n,m,s,h[maxn],cnt;
int lg[maxn<<1],first[maxn],f[maxn<<1][20],dep[maxn<<1],val[maxn<<1],tot;//log值;某个点第一次在欧拉序中出现的位置;st表;欧拉序得到的深度;欧拉序得到的值;树的当前位置;
struct node{
    int to,next;
}b[maxn<<1];
void add(int u,int v){//链式前向星存图
    b[++cnt].to=v;
    b[cnt].next=h[u];
    h[u]=cnt;
}
void dfs(int u,int d,int fa){//当前节点;深度;父亲节点
    dep[++tot]=d,first[u]=tot,val[tot]=u;//记录节点第一次出现时欧拉序及深度
    for(int i=h[u];i;i=b[i].next)//从当前节点向下深搜
        if(b[i].to!=fa){
            dfs(b[i].to,d+1,u);
            dep[++tot]=d,val[tot]=u;//返回时记录欧拉序及深度
        }
}
void st(){
    rep(i,2,tot) lg[i]=lg[i>>1]+1;//前期log处理
    rep(i,1,tot) f[i][0]=i;//当前节点向后2的0次方-1得到的最小深度节点是本身
    rep(i,1,lg[tot])
        rep(j,1,tot+1-(1<<i)){//st表处理方式将一个区间分为两个有覆盖的小区间
            int ta=f[j][i-1];
            int tb=f[j+(1<<(i-1))][i-1];
            if(dep[ta]<=dep[tb]) f[j][i]=ta;//根据小区间最小深度更新大区间最小深度
            else f[j][i]=tb;
        }
}
int main(){
    n=read(),m=read(),s=read();
    int x,y;
    rep(i,1,n-1){
        x=read(),y=read();
        add(x,y),add(y,x);
    }
    dfs(s,1,0);//根节点出发得到欧拉序
    st();
    rep(i,1,m){
        x=read(),y=read();
        x=first[x],y=first[y];//求lca必须为第一次写出现的位置
        if(x>y) swap(x,y);//保证x为小值
        int t=lg[y-x+1];
        int ta=f[x][t];
        int tb=f[y-(1<<t)+1][t];
        if(dep[ta]<=dep[tb]) printf("%d\n",val[ta]);
        else printf("%d\n",val[tb]);
    }
    return 0;
}

 

posted @ 2022-03-07 22:26  NFTB  阅读(135)  评论(0)    收藏  举报