最近公共祖先(LCA)

给定一个定义:x,y的最近公共祖先称之为LCA(x,y)是x和y的所有公共祖先中深度最大的一个节点

我们总结一下它的三种求法:

第一种是最简单的不断向上标记节点,如果x的标记节点和y的标记节点相遇了,就找到了LCA(x,y),复杂度为O(n)

第二种是树上倍增法,其原理是倍增(类比于ST表)

在树上倍增的过程类似于一个动态规划的过程,我们约定F[x,y]表示x的2^k辈祖先,为了简化处理,我们将默认0作为节点不存在,也即是F[x,k]=0。

F[x,0]为x的父辈,F[x,1]为x的父辈的父辈也就是祖父辈,F[x,1]=F[F[x,0],0]。我们将整个过程的一般化,也就是F[x,k]=F[F[x,k-1],k-1](也就是他的2^k辈祖先就是2^(k-1)辈祖先的2^(k-1)辈祖先)k∈[1,log2n]

首先进行预处理,将每个节点的父辈以及父辈的父辈以及父辈的父辈的父辈的父辈...都处理出来

(注意BFS是单调的,每次处理完一个层次,再处理下一个层次,也就是F处理[x,k]时,F[x-1,k]已经被处理完成了,另外就是如果他的祖先不存在,实际上是F[0,k]永远是0,简化处理过程)

  • HDU-2586 How far away?
#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>

#define fore(i,a,b) for(int i=a;i<=b;i++)
#define fort(i,a,b) for(int i=a;i>=b;i--)
#define forg(i,x) for(int i=head[x];i;i=next[i])
#define mem0(i) memset(i,0,sizeof(i))
#define meme(i,a) memset(i,a,sizeof(i))

const int MAXN=50010;

int T, n, m, tot, t;
int f[MAXN][20], d[MAXN], dist[MAXN];
int ver[MAXN*2], next[MAXN*2], edge[MAXN*2], head[MAXN];
std::queue<int>q;

void add(int x,int y,int z){
    ver[++tot]=y, edge[tot]=z, next[tot]=head[x], head[x]=tot;
}

void bfs(){
    q.push(1);
    d[1]=1;
    while(!q.empty()){
        int x=q.front();q.pop();
        forg(i,x){
            int y=ver[i];
            if(d[y]) continue;
            d[y]=d[x]+1;
            dist[y]=dist[x]+edge[i];
            f[y][0]=x;
            fore(j,1,t)
                f[y][j]=f[f[y][j-1]][j-1];
            q.push(y);
        }
    }
}

int lca(int x,int y){
    if(d[x]>d[y]) std::swap(x,y);
    fort(i,t,0) if(d[f[y][i]]>=d[x]) y=f[y][i];
    if(x==y) return x;
    fort(i,t,0) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

int main(){
    std::cin>>T;
    while(T--){
        std::cin>>n>>m;
        t=(int)(log(n)/log(2))+1;
        fore(i,1,n) head[i]=d[i]=0;
        tot=0;
        fore(i,1,n-1){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        bfs();
        fore(i,1,m){
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",dist[x]+dist[y]-2*dist[lca(x,y)]);
        }
    }
    return 0;
}

 第三种方法是利用并查集和深度优先搜索在遍历的基础上进行更新答案,但是问题就是必须离线回答问题,时间复杂度为O(n+m)

#include <iostream>
#include <cstdio>
#include <cmath>
#include <queue>
#include <cstring>
#include <vector>

#define fore(i,a,b) for(int i=a;i<=b;i++)
#define fort(i,a,b) for(int i=a;i>=b;i--)
#define mem0(i) memset(i,0,sizeof(i))
#define meme(i,a) memset(i,a,sizeof(i))

const int MAXN=50010;
const int INF=1<<30;

int T, n, m, tot, t;
int ver[MAXN*2], next[MAXN*2], edge[MAXN*2], head[MAXN];
int fa[MAXN], d[MAXN], vis[MAXN], lca[MAXN], ans[MAXN];
std::vector<int> query[MAXN],query_id[MAXN];

void add(int x,int y,int z){
    ver[++tot]=y, edge[tot]=z, next[tot]=head[x], head[x]=tot;
}

void add_query(int x,int y,int id){
    query[x].push_back(y),query_id[x].push_back(id);
    query[y].push_back(x),query_id[y].push_back(id);
}

int get(int x){
    if(fa[x]==x) return x;
    return fa[x]=get(fa[x]);
}

void tarjan(int x){
    vis[x]=1;
    for (int i = head[x]; i; i = next[i]) {
        int y=ver[i];
        if(vis[y]) continue;
        d[y]=d[x]+edge[i];
        tarjan(y);
        fa[y]=x;
    }
    for(int i=0;i<query[x].size();i++) {
        int y=query[x][i],id=query_id[x][i];
        if(vis[y]==2){
            int lca=get(y);
            ans[id]=std::min(ans[id],d[x]+d[y]-2*d[lca]);
        }
    }
    vis[x]=2;
}

int main(){
    std::cin>>T;
    while(T--){
        std::cin>>n>>m;
        fore(i,1,n){
            head[i]=0,vis[i]=0;
            fa[i]=i;
            query[i].clear();
            query_id[i].clear();
        }
        tot=0;
        fore(i,1,n-1){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
            add(y,x,z);
        }
        
        fore(i,1,m){
            int x,y;
            scanf("%d%d",&x,&y);
            if(x==y) ans[i]=0;
            else{
                add_query(x,y,i);
                ans[i]=INF;
            }
        }

        tarjan(1);

        fore(i,1,m) printf("%d\n",ans[i]);
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/rign/p/10610843.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值