P3379 【模板】最近公共祖先(LCA)


原题链接

P3379
题目类型: 普 及 / 提 高 − {\color{yellow}{普及/提高-}} /
AC记录:Accepted

题目大意

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

第一行包含三个正整数 N , M , S N,M,S N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N − 1 N-1 N1行每行包含两个正整数 x , y x,y x,y,表示 x x x结点和 y y y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 M M M行每行包含两个正整数 a , b a,b a,b,表示询问 a a a结点和 b b b结点的最近公共祖先。

输出格式

输出包含 M M M行,每行包含一个正整数,依次为每一个询问的结果。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

4
4
1
4
4

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
在样例中的图如下。
在这里插入图片描述

数据范围

对于 30 % 30\% 30%的数据, N ≤ 10 , M ≤ 10 N≤10,M≤10 N10,M10
对于 70 % 70\% 70%的数据, N ≤ 10000 , M ≤ 10000 N≤10000,M≤10000 N10000,M10000
对于 100 % 100\% 100%的数据, N ≤ 500000 , M ≤ 500000 N≤500000,M≤500000 N500000,M500000

解题思路

本题我们将要用到倍增算法。
如果是Native(暴力)的 L C T LCT LCT,就是两个节点直接往上跳,跳到同一个节点就可以了。但是,很不幸,洛谷新加了两个点,专门是用来卡直接跳的。(我这两个点都是用倍增吸氧和ios::sync_with_stdio(false)cin.tie(0)才跑了 1.82 s 1.82s 1.82s
那么,用倍增怎么做呢?
我们都知道,平常我们的倍增,都是从小到大跳 2 k 2^k 2k,但是,这里我们要从大到小。原因也很简单,比如说 10 = 8 + 2 10=8+2 10=8+2,如果从小到大,会发现 1 1 1 4 4 4不行,还需要回来继续,但是从大到小直接 10 = 8 + 2 10=8+2 10=8+2就行了。
这里,我们可以提前求出一个数组 l g lg lg,其中 l g i = log ⁡ 2 i lg_i=\log_2i lgi=log2i,可以用lg_2[i]=lg_2[i-1]+int((1<<lg_2[i01])==i) Θ ( n ) \Theta(n) Θ(n)求出,这里就给你们自行理解了。
那么,我们可以设 f i , j f_{i,j} fi,j为节点 i i i的第 2 i 2^i 2i个父节点。如上图,则 f 5 , 0 = 1 f_{5,0}=1 f5,0=1,这可以帮助我们从当前节点快速跳到前面的节点。
但是,该如何算这个 f f f数组呢?
我们可以用一轮 d f s dfs dfs,求出每一个节点的深度,也可以顺便求出他的 f f f数组。这里,我们知道, 2 i = 2 i − 1 + 2 i − 1 2^i=2^{i-1}+2^{i-1} 2i=2i1+2i1,所以,一个节点的第 2 i 2^i 2i个父节点就是他的 第 2 i − 1 2^{i-1} 2i1个父节点 的 第 2 i − 1 2^{i-1} 2i1个父节点


接下来,就是重头戏 倍增 L C A LCA LCA 了。
还是和Native算法一样,先让深度深的节点走到和深度浅的节点一样的深度,这里就可以用我在上面说的从大到小的方法遍历。此时,需要特判一下,因为有可能 x = y x=y x=y,此时 x x x就是原来 x x x y y y L C A LCA LCA。如果不是,就按照我上面说的从大到小的顺序同时跳节点,由于我们跳的是两个节点的公共祖先的下面一个位置,所以最后答案为 f x , 0 f_{x,0} fx,0

上代码

#include<bits/stdc++.h>

using namespace std;

vector<int> road[500010];
bool        vis[500010];
int         gr_fa[500010][30];
int         lg_2[500010];
int         dep[500010];
int         n,m,root;

void dfs(int pos,int fat)
{
    vis[pos]=true;
    dep[pos]=dep[fat]+1;
    gr_fa[pos][0]=fat;
    for(int i=1; i<=lg_2[dep[pos]]-1; i++)
        gr_fa[pos][i]=gr_fa[gr_fa[pos][i-1]][i-1];
    for(int i=0; i<road[pos].size(); i++)
        if(!vis[road[pos][i]])
            dfs(road[pos][i],pos);
    return;
}

void work()
{
    int x,y;
    cin>>x>>y;
    if(dep[x]<dep[y])
        swap(x,y);
    while(dep[x]>dep[y])
        x=gr_fa[x][lg_2[dep[x]-dep[y]]-1];
    if(x==y)
    {
        cout<<x<<endl;
        return;
    }
    for(int i=lg_2[dep[x]]-1; i>=0; i--)
        if(gr_fa[x][i]!=gr_fa[y][i])
            x=gr_fa[x][i],y=gr_fa[y][i];
    cout<<gr_fa[x][0]<<endl;
    return;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m>>root;
    for(int i=1; i<n; i++)
    {
        int x,y;
        cin>>x>>y;
        road[x].push_back(y);
        road[y].push_back(x);
    }
    for(int i=1; i<=n; i++)
        lg_2[i]=lg_2[i-1]+int((1<<lg_2[i-1])==i);
    dfs(root,0);
    // for(int i=1; i<=n; i++)
    // {
    //     for(int j=0; j<lg_2[dep[i]]; j++)
    //         cout<<gr_fa[i][j]<<" ";
    //     cout<<endl;
    // }
    for(int i=1; i<=m; i++)
        work();
    return 0;
}

完美切题 ∼ \sim

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值