洛谷P3379 【模板】最近公共祖先(LCA)(倍增法)

题目链接:P3379 【模板】最近公共祖先(LCA)

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<sstream>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<utility>
#define JOJO cout<<"JoJo"<<endl;
using namespace std;

const long long INF = ~0ull>>2;
const int Inf = 0x3f3f3f3f;
const int alphabet = 26;
const int maxn = 5e5+5;
int head[maxn]; //head[i]记录以i点为起点的最后一条边的下标
int depth[maxn], anc[maxn][25]; //depth[i]记录i点的深度,anc[i][j]记录i点的第2^j个祖先节点的序号
int t = 0;
int S;  //S是树的根结点

/*edge[i].to记录边i的终点,
edge[i].next记录与边i起点相同的上一条边的下标*/
struct tagedge{
    int to, next;
}edge[2*maxn];

void AddEdge(int u, int v)
{
    edge[t].to = v;
    edge[t].next = head[u];
    head[u] = t++;
    return;
}

void Dfs_init(int rt, int par)  //rt指当前结点,par指当前结点的父结点
{
    depth[rt] = depth[par]+1;   //当前结点的深度是其父结点深度+1
    anc[rt][0] = par;   //当前结点的第2^0个即第1个结点,就是其父结点

    /***倍增递推式:anc[rt][i] = anc[ anc[rt][i-1] ][i-1]**
    当前结点的第 2^i 个祖先结点是其 2^(i-1) 个祖先结点的第 2^(i-1) 个祖先结点*/
    for (int i = 1; i <= log(depth[rt]-1)/log(2) ; i++)
        anc[rt][i] = anc[ anc[rt][i-1] ][i-1];

    /*对以该点为起点的每条边的终点进行相同操作(求深度、求祖先结点)
    因为是双向边,所以会出现终点为当前点的父结点的情况,跳过就好*/
    for (int i = head[rt]; i != -1; i = edge[i].next){
        int v = edge[i].to;
        if (v != par)
            Dfs_init(v, rt);
    }
    return;
}

void move_x(int& x, int y)
{
    int h = depth[x] - depth[y];    //h是x结点和y结点相差的深度
    /*用二进制数表示h,因为利用二进制数,可以将时间从n优化到logn,
    结果与用十进制处理相同
    for (int i = 1; i <= h; i++){
       x = anc[x][0];   每次都使x结点移动到它的父结点
    }*/
    for (int i = 0; h > 0; i++){
        if (h & 1)
            x = anc[x][i];  //移动x结点
        h >>= 1;
    }
    return;
}

int LCA(int x, int y)
{
    /*因为move_x()函数中默认x结点比y结点深,所以在之前进行比较和交换*/
    if (depth[x] < depth[y])
        swap(x, y);
    /*因为要求出x结点和y结点的公共祖先结点,
    那么它们最终肯定要在同一个点上,
    这样的话,使x结点和y结点在深度上一起移动就比较方便,
    所以先将x结点和y结点移动到一个深度上*/
    move_x(x, y);

    /*进行特判,
    如果将x结点移动到与y结点同一深度之后两点重合,
    说明y结点就是x结点的祖先结点,
    因此x结点和y结点的最近公共祖先结点就是y结点*/
    if (x == y)
        return y;

    /*从x结点和y结点的最远祖先结点开始判断,
      比较两结点的祖先结点 xx 和 yy,
    1、如果 xx 和 yy 相同,则xx和yy所在的这个结点是x结点和y结点的公共祖先结点,
       但不一定是最近公共祖先结点,所以继续判断较近的祖先结点
    2、如果 xx 和 yy 不同,则最近公共祖先结点一定是xx和yy的最近公共祖先结点,
       问题则转化为求xx和yy的最近公共祖先结点
       (即,将x结点和y结点分别移动到xx和yy的位置)

    为什么从最远祖先结点开始判断,而不是从两结点的父结点(父结点距离两结点最近)开始判断?
    若从两结点的父结点开始判断,
    ①利用anc[x][0, 1, 2, 3···],可能会跳过最近公共祖先结点,到较远的公共祖先结点,且此时很难返回
    ②利用anc[x][0]一个深度一个深度地寻找,效率太低*/
    for (int i = log(depth[x])/log(2)+1; i >= 0; i--){
        if (anc[x][i] != anc[y][i]){
            x = anc[x][i];  //同时移动x结点和y结点,保证它们在同一深度上
            y = anc[y][i];
        }
    }
    return anc[x][0];
}

void init()
{
    fill(head, head+maxn, -1);
    fill(depth, depth+maxn, 0);
    fill(anc[0], anc[0]+maxn*20, 0);
    memset(edge, 0, sizeof(edge));
    t = 0;
    return;
}

int main()
{
    init();

    int N, M;
    scanf("%d%d%d", &N, &M, &S);
    for (int i = 1; i <= N-1; i++){
        int x, y;
        scanf("%d%d", &x, &y);
        AddEdge(x, y);  //建树,因为是无向边,所以x->y和y->x都要建边
        AddEdge(y, x);
    }

    /*Dfs_init() 用dfs进行初始化,求出每个结点的深度和各祖先节点
    从树的根开始初始化*/
    Dfs_init(S, 0);

    while (M--){
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", LCA(x, y));  //LCA(x, y) 求x和y的最近公共祖先结点
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值