倍增求LCA

LCA:即求一棵有根树中两个节点的最近公共祖先。

树上倍增法

树上倍增法是一个很重要的算法。除了求LCA外,在很多问题中都有广泛应用。设F[x,k]表示x的2k倍祖先,即从x向根节点走2k步到达的节点。特别的,若该节点不存在,则令F[x,k] = 0。F[x,0]就是x的父节点,除此之外,对于任意的1 <= k <= log n,F[x,k] = F[F[x,k-1],k-1]。
这就类似于一个动态规划的过程,“阶段”就是节点的深度。因此,我们可以对树进行广度优先遍历,按照层次顺序,在节点入队之前,计算它在F数组中相应的位置。
以上部分是预处理,时间复杂度为O(N log N),之后可以多次对不同的x,y计算LCA,每次询问的时间复杂度为O(log N)。
算法步骤:

  1. 设d[x] 表示 x 的深度。设 d[x] >= d[y] (否则可以交换x,y)。
  2. 用二进制拆分思想,把 x 向上调整到与 y 同一深度。
  3. 若此时 x = y,说明已经找到了LCA,LCA就等于y。
  4. 用二进制拆分思想,把x,y同时向上调整,并保持深度一致且二者不相会。
  5. 此时x,y必定只差一步就相会了,他们的父节点F[x,0]就是LCA。

代码模板: HDU2586

#include<cstdio>
#include<cstring>
const int N = 2*41000;
int head[N],ver[N],nex[N],edge[N],tot = 0;
int deep[N],anc[N][25],dis[N];
void swap(int &x,int &y){x ^= y;y^=x;x^=y;}
void addEdge(int x,int y,int z){
    ver[++tot] = y,edge[tot] = z;
    nex[tot] = head[x],head[x] = tot;
}
void dfs(int x){
    /*
        1.初始化anc数组,
        2.初始化deep数组
        3.初始化dis数组
        以上三个都可以通过dfs来实现
    */
    for(int i = 1;i <= 22;i++)
        anc[x][i] = anc[anc[x][i-1]][i-1];
    for(int i = head[x]; i;i = nex[i]){
        int y = ver[i],z = edge[i];
        if(dis[y] || y == 1) continue;//防止重复访问
        deep[y] = deep[x]+1;
        dis[y] = dis[x] + z;
        anc[y][0] = x;
        dfs(y);
    }
}
int Lca(int u,int v){
    /*
        先把深度调到一致,再按照二进制拆分思想找寻最近公共祖先
    */
    if(deep[u] < deep[v]) swap(u,v);    //u是较深的节点
    for(int i = 22;i >= 0;i--){
        if(deep[anc[u][i]] >= deep[v]) u = anc[u][i];
    }
    if(u == v) return u;
    for(int i = 22;i >= 0;i--){
        if(anc[u][i] != anc[v][i]){
            u = anc[u][i];
            v = anc[v][i];
        }
    }
    return anc[u][0];
}
int ask(int u,int v){
    return dis[u] + dis[v] -2*dis[Lca(u,v)];
}
int main(){
    int t,n,m;
    scanf("%d",&t);
    while(t--){
        memset(dis,0,sizeof dis);
        memset(deep,0,sizeof deep);
        memset(anc,0,sizeof anc);
        memset(head,0,sizeof head);//head也要初始化
        scanf("%d%d",&n,&m);tot = 0;
        for(int i = 1,x,y,z;i < n;i++){
            scanf("%d%d%d",&x,&y,&z);
            addEdge(x,y,z);
            addEdge(y,x,z);
        }
        dfs(1);
        for(int i = 1,x,y;i <= m;i++){
            scanf("%d%d",&x,&y);
            printf("%d\n",ask(x,y));
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

迷亭1213

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值