题目描述:
在郊区有 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
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
#include
#include
#include
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 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;
}