飞行路线 |分层图初步学习

链接:https://ac.nowcoder.com/acm/problem/15073
来源:牛客网

题目描述

Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在n个城市设有业务,设这些城市分别标记为0到n-1,一共有m种航线,每种航线连接两个城市,并且航线有一定的价格。
Alice和Bob现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多k种航线上搭乘飞机。那么Alice和Bob这次出行最少花费多少?

输入描述

数据的第一行有三个整数,n,m,k,分别表示城市数,航线数和免费乘坐次数。
第二行有两个整数,s,t,分别表示他们出行的起点城市编号和终点城市编号。(0 ≤ s,t < n)
接下来有m行,每行三个整数,a,b,c,表示存在一种航线,能从城市a到达城市b,或从城市b到达城市a,价格为c。(0 ≤ a,b < n,a与b不相等,0 ≤ c ≤ 1000)

对于30%的数据,2<=n<=50,1<=m<=300,k=0;
对于50%的数据,2<=n<=600,1<=m<=6000,0<=k<=1;
对于100%的数据,2<=n<=10000,1<=m<=50000,0<=k<=10.

输出描述

只有一行,包含一个整数,为最少花费。

分析

为啥这个题目要写一下呢,主要这个建图方式以前没遇到过,,遇到过差不多的题目也是用二分卡过去的

这里要涉及到一种特殊的建图方式——分层图

分层图主要用于题目说可以免费建k条边的时候使用,在建图的时候多建立k张图,如果x和y之间连接了一条边,并且距离为z的话,我们就可以在每相邻的两层图之间,对上一层的x和下一层的y之间建立一条边,距离为0,这样如果我们想从第一层图转移到第二层图,就需要从一条距离为0的边进行转移,同理,如果我想从第一层图移动到第k + 1层图,就需要走k次距离为0的边

为了方便理解,我们来画一个模拟图,请不要吐槽这张图为什么这么难看(虽然确实很难看)

在这里插入图片描述
所以我们可以保证从第一层图的起点到第k + 1层图的终点走过了k条距离为0的点,因为是求最小,而且边权值都为正,所以这k条边权为0的边肯定是需要全部用上

注:这里写的有点问题,不一定k条边权为0的边都得用到,可能最短路会出现在中间某一层的终点,可以尝试在遍历每一层的终点取最小值或者每两层的终点之间连一条边,如果直接输出第k + 1层图终点的值,牛客上面能过,洛谷上面过不了,因为加了一组hack数据(毒瘤数据)

另外提一句,我试了一下spfa在用在这里会被卡掉,所以改成了堆优化的dijkstra算法,但是不是所有的分层图题目都不能用spfa,这个我不太确定,保险起见还是不要用为好,我写这道题的时候就被卡傻了,后来套了个dijkstra模版直接ac了

所以,最后的代码

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
#define debug(x) cout << #x << ':' << x << endl;
  
using namespace std;
typedef pair<int,int> PII;
  
const int N = 1000005,M = 2 * 500000 * 10 + 10;;
int idx,h[N],ne[M],e[M],w[M];
int d[N];
bool st[N];
int n,m,k;
int S,T;
  
void add(int x,int y,int z){
    e[idx] = y,w[idx] = z,ne[idx] = h[x],h[x] = idx++;
}
  
void dij(){
    memset(d,0x3f,sizeof d);
    d[S] = 0;
    priority_queue<PII,vector<PII>,greater<PII>> Q;
    Q.push({0,S});
    while(Q.size()){
        auto p = Q.top();
        Q.pop();
        int t = p.second;
        if(st[t]) continue;
        st[t] = true;
        for(int i = h[t];i != -1;i = ne[i]){
            int j = e[i];
            if(d[j] > d[t] + w[i]){
                d[j] = d[t] + w[i];
                Q.push({d[j],j});
            }
        }
    }
}
  
int main(){
    memset(h,-1,sizeof h);
    scanf("%d%d%d%d%d",&n,&m,&k,&S,&T);
    while(m--){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
        for(int i = 1;i <= k;i++){
            add(x + (i - 1) * n, y + i * n, 0);
            add(y + (i - 1) * n, x + i * n, 0);
            add(x + i * n, y + i * n, z);
            add(y + i * n, x + i * n, z);
        }
    }
    dij();
    int ans=0x3f3f3f3f;
    for(int i=0;i<=k;i++)
        ans=min(d[T+i*n],ans);
    printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值