AcWing 340 通信线路

题目描述:

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站Ai和Bi。

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。

现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费Li。

电话公司正在举行优惠活动。

农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。

农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。

求至少用多少钱可以完成升级。

输入格式

第1行:三个整数N,P,K。

第2..P+1行:第 i+1 行包含三个整数Ai,Bi,Li。

输出格式

包含一个整数表示最少花费。

若1号基站与N号基站之间不存在路径,则输出”-1”。

数据范围

0≤K<N≤1000,
1≤P≤10000,
1≤Li≤1000000

输入样例:

5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6

输出样例:

4

分析:

本题的题意是找到一条路径,边权最大的k条边忽略,第k + 1大的边权就作为该条路径的代价,求最小代价是多少,换而言之,就是求从起点到终点的所有路径中第k + 1大的边权最小是多少。虽然最后写出来很简单,但是想到这个思路是相当不容易的。算法竞赛进阶指南上给出了动态规划的思路和二分的思路,本题也可以用分层图去做。相对于动态规划和分层图的思路,二分的解法思维的难度和求解的时间复杂度都是比较低的。

拿到一道陌生的题,没思路的情况下首先考虑暴力求解的思路,求第k + 1长的边最小是多少,暴力求解我们只能去枚举所有的路径,然后依次求出各种路径中第k + 1长的边是多少,比较下求出最小的是多少。显然要想求从起点到终点的所有路径是很不容易的,而且复杂度也相当高,暴力做法实现起来不容易。

本题的难点有两个,第一个是想到用二分去求解,第二个是将题目转化为双端队列BFS问题。首先,我们思考下二分一般应用于上面情况下,很简单,就是给一组数,先判断下要找的数在不在左半部分,在就在左半部分继续二分,不在就到右半部分去找。可以用二分取解决的问题要满足两个特性,其一是解在一定的范围内,以便我们确定二分的左右端点;其二是这组数据具有一定的单调性。这种单调性既可以是显性的,比如有序的数组,也可以是隐性的,只要知道中间数满不满足条件就可以确定下一步查找的范围。既然我们不知道这题的解如何求,那么是否有办法说给我x元钱,我有没有办法确定这么多钱能否去升级一条路径呢?肯定是有办法的。题目给定的L在1到100w间,这就说明本题的解一定在0到100w之间,否则就是无解,输出-1。解为0的情况是这条路径上的边不超过k条,意味着不用花钱就可以升级线路;无解的情况是从起点无法到达终点。既然本题的解有一定的范围,并且如果x元能够升级某条路径,那么解一定不会超过x,这就是单调性,也就意味着本题可以用二分解决。

接着来谈第二个难点,我们如何去确定x元钱能否升级一条线路,给我们一条路径,我们把这条路径上的边权与x意义比较,只要大于x的边不超过k条就说明可以用不超过x元去升级这条线路。继续思考会发现,我们不关心这条路径上的每条边的具体权值是多少,只关心其与x的大小关系,因此整个图上的边就分为两类,边权大于x的和不大于x的,大于x的边我们将其边权视为1,否则边权视为0,只要从起点到终点的最短路径长度不超过k就说明x元升级线路是可行的。边权只有0和1两种情况的最短路问题可以用双端队列BFS解决,双端队列BFS相关的问题题解见AcWing 175 电路维修,如果不想再去看那么多的文字,我这里简单的解释下双端队列BFS的思想。dijkstra算法的思路是维护一个小根堆,堆顶元素的距离永远是最小的,然后不断取出堆顶元素去松弛周围点的距离。而边权只有01两种情况的图我们无需维护一个小根堆,只需要维护一个双端队列,保证双端队列的队头元素离起点的距离永远是最小的即可,为此,从起点开始我们将起点加入队列,然后尝试去松弛周围的点,只要周围的点还未出队过,并且可以被松弛,就将该点松弛后加入队列中,边权是0就加入队头,边权是1就加入队尾。这就是双端队列BFS的基本思路。(详细介绍还是看之前的题解吧)。

整理下本题的求解思路,在0到100w间二分答案,每次二分时通过双端队列BFS的方法判断起点到终点的最短路径是否不超过mid,是的话就在左半部分继续二分,否则在右半部分二分,直到找到答案为止。在其中任意一次BFS的过程中,一旦发现BFS完终点离起点的距离还是无穷大。说明终点不可达,直接输出-1终止程序。总的代码如下:

#include <iostream>
#include <cstring>
#include <deque>
#include <algorithm>
using namespace std;
const int N = 1005,M = 20005;
int idx,h[N],e[M],w[M],ne[M];
int m,n,k,d[N];
deque<int> q;
bool st[N];
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool bfs(int lim){
    memset(d,0x3f,sizeof d);
    memset(st,false,sizeof st);
    q.push_front(1);
    d[1] = 0;
    while(q.size()){
        int u = q.front();
        q.pop_front();
        if(st[u])   continue;
        st[u] = true;
        for(int i = h[u];~i;i = ne[i]){
            int j = e[i];
            int dist = d[u] + (w[i] > lim);
            if(dist < d[j]){
                d[j] = dist;
                if(w[i] > lim)  q.push_back(j);
                else    q.push_front(j);
            }
        }
    }
    if(d[n] == 0x3f3f3f3f){
        puts("-1");
        exit(0);
    }
    return d[n] <= k;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    int a,b,c;
    memset(h,-1,sizeof h);
    for(int i = 0;i < m;i++){
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    int l = 0,r = 1000000;
    while(l < r){
        int mid = l + r >> 1;
        if(bfs(mid))    r = mid;
        else    l = mid + 1;
    }
    printf("%d\n",l);
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值