题目大意
郊区有
n
n
n 座通信基站,
p
p
p 条双向电缆,第
i
i
i 条电缆连接基站
A
i
A_i
Ai 和
B
i
B_i
Bi。特别地,
1
1
1 号基站是通信公司的总站,
n
n
n 号基站位于一座农场当中。现在,农场主希望对通信线路进行升级,其中升级第
i
i
i 条电缆需要花费
L
i
L_i
Li。
电话公司正在举行优惠活动。农场主可以指定一条从
1
1
1 号基站到
n
n
n 号基站的路径,并指定路径上不超过
k
k
k 条电缆,由电话公司免费提供升级服务。农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。求至少用多少钱能完成升级。
输入格式
第一行三个整数
n
,
p
,
k
n,p,k
n,p,k。
接下来
p
p
p 行,每行三个正整数
a
i
,
b
i
,
l
i
a_i,b_i,l_i
ai,bi,li 。
输出格式
仅包含一个整数,表示在该项目上的最小支出,若任务不可能完成,则输出 − 1 -1 −1。
输入样例
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 k 大的数进行免费,再在剩下的里面挑一个最大的。于是问题就转化为寻找一条路径使其中第 k + 1 k+1 k+1 大的数最小,这不明显的二分吗?
但是问题又来了,怎么检验呢?显然我们要验证是否存在一条路径第
k
+
1
k+1
k+1 大的数是否小于等于我们选择的数(假设为
d
=
6
d=6
d=6 ),我们可以把所有大于
d
d
d 的数标记为
1
1
1 ,小于等于
d
d
d 的数标记为
0
0
0 。
这样我们可以用最短路来寻找一条边权总和最小的路径,即大于 d d d 是需要动用免费的,路过时加 1 1 1,如此一来就可以找出当前情况下免费次数最少的路径,随后判断它是否大于 k k k 即可。
核心代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+10;
struct node{
int to,cost;
};
int n,p,k,l,r,mid,dis[N];
bool v[N];
vector<node>g[N];
deque<int>q;
inline bool check(int x){
//spfa
memset(dis,0x3f,sizeof(dis));
memset(v,0,sizeof(v));
dis[1]=0;v[1]=true;
q.push_back(1);
while(!q.empty()){
int u=q.front();
q.pop_front();
v[u]=false;//允许重复激活
for(node i:g[u]){
int j=i.to,w=i.cost>x;//w为0或1
if(dis[j]>dis[u]+w){
dis[j]=dis[u]+w;
if(v[j]==true) continue;
v[j]=true;
if(w==0)
q.push_front(j);
else
q.push_back(j);
}
}
}
if(dis[n]>k) return false;
return true;
}
int main(){
ios::sync_with_stdio(false);
//寻找一条路径使得其中第k+1大的权值最小
//二分第k+1大的权值,假设为6,则途中大于6的值标记为1
//代表经过这条边需要一次免费,路过时统计免费次数,小于等于6标记为0
//验证时双端队列让为0的边优先扩展
cin>>n>>p>>k;
for(int i=1,ai,bi,li;i<=p;i++){
cin>>ai>>bi>>li;
g[ai].push_back({bi,li});
g[bi].push_back({ai,li});
}
l=0,r=1e6+1;
//为什么是1e6+1?因为可能1和n根本不连通
while(l<r){
mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
if(l==1e6+1)
l=-1;
cout<<l;
return 0;
}