codeforces 832 D Misha, Grisha and Underground(倍增)

96 篇文章 0 订阅
51 篇文章 0 订阅

题意:

给出n个点的树,q个询问,每次询问给出3个数x,y,z问选两个点作为起点,到第三个点的路径有多少个点重合。


解题 思路:

distance(x,y)=distance(x,root)+distance(y, root)-2*distance(lca(x,y),root).

x,y为起点时,答案就是(dis(x,z)+dis(y,z)-dis(x,y))/2 。


重点讲一下倍增吧,第一次写,求LCA简直不能更简单。


倍增顾明思义就是成倍增大。两个点往上找lca的时候就是一个每次往上的距离成倍变大的一个过程。


倍增求LCA需要预处理祖先数组。fa[i][j],表示第i个点的第2^j个祖先,j从0开始。

这个处理起始非常简单,fa[i][j]=fa[fa[i][j-1][i].因为第2^j个祖先等价于当前点第2^(j-1)个祖先的第2^(j-1)个祖先,一次跑一遍就可以了。


另外还需要处理处每个点的深度,这个简单,必要的话要处理下到根节点的距离,没有边权的时候其实就是深度。


求a和b的lca的时候,先让深度大的节点先往上跳,跳的过程可以用二进制优化,设深度差为dif,把dif二进制拆分,只需要对为1的对应位j跳到第2^j个祖先就可以。


当深度一样时,判断下当前是否a和b是否相等,不相等让a和b同时往上跳,j从高到低判断下第2^j个祖先是否相同,相同就不往上跳,知道碰到祖先不同再往上跳。循环结束后,a和b的父亲就是lca了。


代码:

#include <bits/stdc++.h>
#define ps push_back
using namespace std;
const int maxn=1e5+5;
int dp[maxn][22];
int dep[maxn];

vector<int>edg[maxn];

void dfs(int x, int fa)
{
    dep[x]=dep[fa]+1;
    int v;
    if(fa)
    for(int i=1; i<20; i++)
    {
        dp[x][i]=dp[dp[x][i-1]][i-1];   
    }
    for(int i=0; i<(int)edg[x].size(); i++)
    {
        v=edg[x][i];
        if(v==fa)continue;
        dp[v][0]=x;
        dfs(v, x);
    }
    return;
}


int dis(int a, int b)
{
    int x=a, y=b;
    if(dep[a]<dep[b])
    {
        swap(a,b);
    }

    int i, dif=dep[a]-dep[b];
    for(i=0; (1<<i)<=dif; i++)
    {
        if(dif&(1<<i))
        {
            a=dp[a][i];
        }       
    }

    if(a==b)
    {
        return dep[x]+dep[y]-2*dep[a];
    }
    for(i=20; i>=0 && a!=b; i--)
    {
        if(dp[a][i]==dp[b][i])continue;
        a=dp[a][i]; b=dp[b][i];
    }

    a=dp[a][0];
    return dep[x]+dep[y]-2*dep[a];
}

int query(int x, int y, int z)
{
//    printf("%d %d %d\n", dis(x, z), dis(y, z), dis(x, y));
    return (dis(x, z)+dis(y, z)-dis(x, y))/2;
}


int main()
{
    int n, x, q, i, j;   
    cin>>n>>q;
    for(i=2; i<=n; i++)
    {
        scanf("%d", &x);
        edg[x].ps(i);
    }
    dp[1][0]=1;
    dep[1]=1;
    dfs(1, 0);
//    for(i=1; i<=3; i++)printf("%d\n", dp[i][0]);
    int y, z;
    for(i=1; i<=q; i++)
    {
        scanf("%d%d%d", &x, &y, &z);
        printf("%d\n", max(max(query(x, y, z), query(x, z, y)), query(z, y, x))+1);
    }

}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值