CodeForces - 1051F 【LCA + 最短路】

传送门

大概题意
给你一个包含n个点m条无向边的图,多次询问最短路(保证每两个点都连通,m - n ≤ 20 )

思路
我们要注意题面上给你的条件( m - n ≤ 20 ),这是一个很重要的信息。我们知道含有n个结点的树包含 n - 1 条边,这道题相当于在树上又多加了几条边。如果给我们一棵树,多次询问最短路那么答案等于 dis[ s ] + dis [ e ] - 2 * dis[ lca ( s , e ) ]。当然我们的求解的最短路可能所有边都在树上,所以上面这种情况也是答案的一种。还有一种情况就是,最短路会经过非树边。这个问题我们解决呢?我们可以求解出所有非树边上面结点的最短路(最多42个点),相当于我们利用这一个点作为转折点。那么答案就可能等于
dis[ s - > 转折点] + dis [ 转折点 - > e ]。我们为什么只需要一个转折点呢?因为我们求解的是最短路,dis[ s - > 转折点] 和 dis [ 转折点 - > e ] 都是最优的情况,我们只需要取 所有转折点的最优情况和第一种情况 的最小值。

附上代码:
 

///#include<bits/stdc++.h>
///#include<unordered_map>
///#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<queue>
#include<set>
#include<stack>
#include<map>
#include<new>
#include<vector>
#define MT(a,b) memset(a,b,sizeof(a));
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double pai=acos(-1.0);
const double E=2.718281828459;
const ll mod=1e9+7;
const ll INF=0x3f3f3f3f3f3f;

int n,m,op;

struct node
{
    int e;
    ll c;
    int p;
    int vis;
    bool friend operator<(node a,node b)
    {
        return a.c>b.c;
    }
}load[200005];
int head[100005],sign;
bool flag[100005];

void add_edge(int s,int e,ll c)
{
    load[++sign]=node{e,c,head[s],0};
    head[s]=sign;
}

int grand[100005][20],N;
int depth[100005];
ll dis[100005];

void dfs(int s)
{
    flag[s]=1;
    for(int i=1;i<=N;i++)
        grand[s][i]=grand[grand[s][i-1]][i-1];
    for(int i=head[s];~i;i=load[i].p)
    {
        int e=load[i].e;
        if(!flag[e])
        {
            ///将这条边标记
            load[i].vis=1;
            (i&1)?load[i+1].vis=1:load[i-1].vis=1;
            depth[e]=depth[s]+1;
            grand[e][0]=s;
            dis[e]=dis[s]+load[i].c;
            dfs(e);
        }
    }
}

int get_lca(int a,int b)
{
    if(depth[a]>depth[b])
        swap(a,b);
    for(int i=N; i>=0; i--)
    {
        if(depth[b]>=depth[a]&&depth[grand[b][i]]>=depth[a])
            b=grand[b][i];
    }
    for(int i=N; i>=0; i--)
    {
        if(grand[a][i]!=grand[b][i])
        {
            a=grand[a][i];
            b=grand[b][i];
        }
    }
    return a==b?a:grand[b][0];
}

int add[100],up;
ll shorts[50][100005];///最多42个点,千万不要开小了
void dij(int sub,int s)
{
    shorts[sub][s]=0;
    bool vis[100005];
    memset(vis,0,sizeof(vis));
    priority_queue<node>q;
    q.push(node{s,0});
    while(!q.empty())
    {
        node w=q.top();
        s=w.e;
        q.pop();
        if(vis[s])
            continue;
        vis[s]=1;
        for(int i=head[s];~i;i=load[i].p)
        {
            int e=load[i].e;
            if(!vis[e]&&shorts[sub][e]>shorts[sub][s]+load[i].c)
            {
                shorts[sub][e]=shorts[sub][s]+load[i].c;
                q.push(node{e,shorts[sub][e]});
            }
        }
    }
}

void init()
{
    N=log2(n);
    up=sign=0;
    memset(head,-1,sizeof(head));
    memset(grand,0,sizeof(grand));
    memset(depth,0,sizeof(depth));
    memset(dis,0,sizeof(dis));
    memset(flag,0,sizeof(flag));
    fill(shorts[0],shorts[0]+sizeof(shorts)/sizeof(ll),INF);
    depth[0]=-1;
}

int main()
{
    int s,e;
    long long  c;
    scanf("%d %d",&n,&m);
    init();
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %lld",&s,&e,&c);
        add_edge(s,e,c);
        add_edge(e,s,c);
    }
    dfs(1);
    for(int i=1;i<=n;i++)
    {
        for(int j=head[i];~j;j=load[j].p)
        {
            ///如果没有被标记,则说明是非树边
            if(!load[j].vis)
            {
                add[++up]=i;
                add[++up]=load[j].e;
            }
        }
    }
    ///去重
    sort(add+1,add+1+up);
    int d=unique(add+1,add+1+up)-(add+1);
    ///最短路
    for(int i=1;i<=d;i++)
        dij(i,add[i]);
    scanf("%d",&op);
    while(op--)
    {
        scanf("%d %d",&s,&e);
        ll ans=INF;
        ///第二种情况
        for(int i=1;i<=d;i++)
            ans=min(ans,shorts[i][s]+shorts[i][e]);
        ///第一种情况
        int x=get_lca(s,e);
        ans=min(ans,dis[s]+dis[e]-2*dis[x]);
        printf("%lld\n",ans);
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值