【基环树】 E.Shortest Path Sum 牛客

该博客讨论了一种无向连通图的最短路径问题,其中涉及基环树的概念。对于每次询问三个点x, y, z,需要找到一个点k,使得k到这三个点的距离之和最小。解决方案包括三种情况分析:所有点在同一棵树上、两点在不同树上、三点在三棵树上。在前两种情况下,答案可以通过LCA(最近公共祖先)计算,第三种情况则转化为环上的点的问题。博客提供了相应的证明思路和代码实现。" 107968003,8070089,web自动化测试实践:XPath与CSS选择器解析,"['Python', 'selenium', 'web自动化']
摘要由CSDN通过智能技术生成

题目https://ac.nowcoder.com/acm/contest/12986/E

题意:给出一个 n n n个点 n n n条边的无向连通图,有 q q q次询问,每次询问给出三个点 x , y , z x,y,z x,y,z,求一个点 k k k x , y , z x,y,z x,y,z三点距离之和最小。

思路
首先看到 n n n个点 n n n条边的无向连通图,这显然是一个基环树,每次询问三个点,那么最多只有三种情况

  • 三个点在一棵树上,那么答案就是两两之间距离之和除以 2 2 2
  • 三个点分别在两颗树上,那么点 k k k的位置一定在同一颗树上两点的 L c a Lca Lca上。
    这个简单证明一下,首先假设 k k k L c a ( x , y ) Lca(x,y) Lca(x,y)

k k k的位置如果在 L c a ( x , y ) Lca(x,y) Lca(x,y)下面,会多加一段距离
在这里插入图片描述

假设 k k k L c a ( x , y ) Lca(x,y) Lca(x,y) z z z之间,也会有重复距离
在这里插入图片描述

  • 最后一种情况就是三个点在三棵树上,那么我们就可以抽象成三个在环上的点的问题,就可以直接计算了

代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define pb push_back
#define MAXN 100005
using namespace std;

typedef  long long ll;
typedef unsigned long long ull;
typedef pair<int,int>P;
vector<int>G[MAXN];
int in[MAXN];//入度
int n;
vector<int>vp;//按顺序存环上的点
int vis[MAXN];
int pos[MAXN];//环上的点在vp数组里的下标
int pre[MAXN],dep[MAXN];//记录点的根,点的深度
int fa[MAXN][25];//预处理LCA
void topo()//拓扑排序
{
    queue<int>q;
    for(int i=1;i<=n;i++)if(in[i]==1)q.push(i);
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int to:G[x])
        {
            in[to]--;
            if(in[to]==1)q.push(to);
        }
    }
}
void dfs(int x,int f,int rt)//计算每个点深度,根,预处理fa数组
{
    pre[x]=rt;
    fa[x][0]=f;
    for(int i=1;i<=20;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int to:G[x])
    {
        if(to==f||in[to]>1)continue;
        dep[to]=dep[x]+1;
        dfs(to,x,rt);
    }
}
int lca(int u,int v)
{
    if(dep[u]<dep[v])swap(u,v);
    for(int i=20;i>=0;i--)if(dep[fa[u][i]]>=dep[v])u=fa[u][i];
    if(u==v)return u;
    for(int i=20;i>=0;i--)if(fa[u][i]!=fa[v][i])
    {
        u=fa[u][i];
        v=fa[v][i];
    }
    return fa[u][0];
}
void dfs1(int x)//按顺序把环上的点存进vp
{
    vp.pb(x);
    vis[x]=1;
    for(int to:G[x])
    {
        if(vis[to]||in[to]!=2)continue;
        dfs1(to);
    }
}

int main()
{
    int q;scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
    {
        int u,v;scanf("%d%d",&u,&v);
        G[u].pb(v);
        G[v].pb(u);
        in[u]++;
        in[v]++;
        pre[i]=i;
    }
    topo();
    for(int i=1;i<=n;i++)
        if(in[i]==2)dfs(i,0,i);
    for(int i=1;i<=n;i++)
    {
        if(in[i]==2)
        {
            dfs1(i);
            break;
        }
    }
    for(int i=0;i<(int)vp.size();i++)pos[vp[i]]=i+1;
    int sum=vp.size();
    while(q--)
    {
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        int ans=0;
        if(pre[x]==pre[y]&&pre[x]==pre[z])//三个点在同一颗树上
        {
            ans+=dep[x]+dep[y]-2*dep[lca(x,y)];
            ans+=dep[x]+dep[z]-2*dep[lca(x,z)];
            ans+=dep[z]+dep[y]-2*dep[lca(z,y)];
            if(ans%2)ans++;
            ans/=2;
        }
        else if(pre[x]==pre[y])//x,y在一棵树上
        {
            ans+=dep[x]+dep[y]-dep[lca(x,y)]+dep[z];
            int tmp=abs(pos[pre[x]]-pos[pre[z]]);
            ans+=min(tmp,sum-tmp);
        }
        else if(pre[x]==pre[z])//x,z在一棵树上
        {
            ans+=dep[x]+dep[z]-dep[lca(x,z)]+dep[y];
            int tmp=abs(pos[pre[x]]-pos[pre[y]]);
            ans+=min(tmp,sum-tmp);
        }
        else if(pre[y]==pre[z])//y,z在一棵树上
        {
            ans+=dep[y]+dep[z]-dep[lca(y,z)]+dep[x];
            int tmp=abs(pos[pre[y]]-pos[pre[x]]);
            ans+=min(tmp,sum-tmp);
        }
        else//分别在三棵树上
        {
            ans+=dep[x]+dep[y]+dep[z];
            int tmp[5];
            tmp[1]=pos[pre[x]];tmp[2]=pos[pre[y]],tmp[3]=pos[pre[z]];
            sort(tmp+1,tmp+4);
            ans+=min(tmp[3]-tmp[1],min(sum-tmp[3]+tmp[2],sum-tmp[2]+tmp[1]));
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值