|
|
## [$AcWing$ $340$. 通信线路](https://www.acwing.com/problem/content/342/)
|
|
|
|
|
|
### 一、题目描述
|
|
|
在郊区有 $N$ 座通信基站,$P$ 条 **双向** 电缆,第 $i$ 条电缆连接基站 $A_i$ 和 $B_i$。
|
|
|
|
|
|
特别地,**$1$号基站是通信公司的总站** (起点),**$N$号基站** (终点) 位于一座农场中。
|
|
|
|
|
|
现在,农场主希望对通信线路进行升级,其中升级第 $i$ 条电缆需要花费 $L_i$
|
|
|
|
|
|
电话公司正在举行优惠活动
|
|
|
|
|
|
农产主可以指定一条从 $1$ 号基站到 $N$ 号基站的路径,并指定路径上不超过 $K$ 条电缆,由电话公司 **免费** 提供升级服务
|
|
|
|
|
|
农场主只需要支付在该路径上 **剩余的电缆中**,**升级价格最贵** 的那条电缆的花费即可
|
|
|
|
|
|
求 **至少用多少钱** 可以完成升级
|
|
|
|
|
|
|
|
|
**输入格式**
|
|
|
第 $1$ 行:三个整数 $N,P,K$。
|
|
|
|
|
|
第 $2..P+1$ 行:第 $i+1$ 行包含三个整数 $A_i,B_i,L_i$。
|
|
|
|
|
|
**输出格式**
|
|
|
包含一个整数表示最少花费。
|
|
|
|
|
|
若 $1$ 号基站与 $N$ 号基站之间不存在路径,则输出 $−1$。
|
|
|
|
|
|
|
|
|
**数据范围**
|
|
|
$0≤K<N≤1000,1≤P≤10000,1≤L_i≤1000000$
|
|
|
|
|
|
**输入样例**:
|
|
|
```cpp {.line-numbers}
|
|
|
5 7 1
|
|
|
1 2 5
|
|
|
3 1 4
|
|
|
2 4 8
|
|
|
3 2 3
|
|
|
5 2 9
|
|
|
3 4 7
|
|
|
4 5 6
|
|
|
```
|
|
|
|
|
|
**输出样例**:
|
|
|
```cpp {.line-numbers}
|
|
|
4
|
|
|
```
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
### 二、题目解析
|
|
|
|
|
|
#### 理解题意
|
|
|
找一条路径,边权最大的$k$条边忽略,**第$k + 1$大的边权作为该条路径的代价**,**求最小代价**
|
|
|
|
|
|
|
|
|
#### 思考过程
|
|
|
|
|
|
① 从$1$号点出发,没有路可以到达$n$点, **无解**,输出$-1$
|
|
|
|
|
|
② 如果 **最短路径边数**(**注意:不是路径的加权和**)不超过$k$条,含义:不用花钱就可以升级线路, 输出$0$
|
|
|
|
|
|
③ 上面 ②中给我们提出了一个新概念:**路径边数**,我们知道,如果想计算获取 **路径边数**,常见的办法是设置边权为$1$。 那是不是所有边都设置为边权为$1$呢?好像不行,因为这样的话,那人家还给你修每条路径的钱数就没用上啊,而且你也没有办法求出你的最小支出啊,此路不通。
|
|
|
|
|
|
④ 这就很纠结啊:不设边权为$1$,无法知道路径长度;全设边权为$1$,就会丢失关键信息。只能是设置 **部分** 边权为$1$。
|
|
|
|
|
|
⑤ 那啥样的边权为$1$,啥样的边权为$0$呢?还得用上真实的边权概念!此时,有如下猜想:
|
|
|
> **如果给我$mid$元钱,我有没有办法确定这么多钱能否够完成升级一条线路呢?**
|
|
|
> 这个简单,我们可以视真实边权大于$mid$的设置 **虚拟边权** 为$1$,反之设为$0$
|
|
|
> 然后在这个图上用$Dijkstra$求最短路径,也就是最短路径长度:
|
|
|
> - 如果最短路径的长度值大于$k$,说明$mid$小了,再调大一点
|
|
|
> - 如果最短路径的长度值不大于$k$,说明$mid$大了,再调小一点
|
|
|
|
|
|
噢,原来需要 **二分答案**
|
|
|
|
|
|
### 三、$Code$
|
|
|
```cpp {.line-numbers}
|
|
|
#include <bits/stdc++.h>
|
|
|
using namespace std;
|
|
|
typedef pair<int, int> PII;
|
|
|
const int INF = 0x3f3f3f3f;
|
|
|
const int N = 1010; // 1000个点
|
|
|
const int M = 20010; // 10000条,记录无向边需要两倍空间
|
|
|
int idx, h[N], e[M], w[M], ne[M];
|
|
|
void add(int a, int b, int c) {
|
|
|
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
|
|
|
}
|
|
|
int n; //点数
|
|
|
int m; //边数
|
|
|
bool st[N]; //记录是不是在队列中
|
|
|
int k; //不超过K条电缆,由电话公司免费提供升级服务
|
|
|
int dist[N]; //记录最短距离
|
|
|
// u指的是我们现在选最小花费
|
|
|
bool check(int x) {
|
|
|
memset(st, false, sizeof st);
|
|
|
memset(dist, 0x3f, sizeof dist);
|
|
|
priority_queue<PII, vector<PII>, greater<PII>> q;
|
|
|
dist[1] = 0;
|
|
|
q.push({0, 1});
|
|
|
|
|
|
while (q.size()) {
|
|
|
PII t = q.top();
|
|
|
q.pop();
|
|
|
int d = t.first, u = t.second;
|
|
|
if (st[u]) continue;
|
|
|
st[u] = true;
|
|
|
for (int i = h[u]; ~i; i = ne[i]) {
|
|
|
int j = e[i], v = w[i] > x; //如果有边比我们现在选的这条边大,那么这条边对方案的贡献为1,反之为0
|
|
|
if (dist[j] > d + v) {
|
|
|
dist[j] = d + v;
|
|
|
q.push({dist[j], j});
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
//如果按上面的方法计算后,n结点没有被松弛操作修改距离,则表示n不可达
|
|
|
if (dist[n] == INF) {
|
|
|
puts("-1"); //不可达,直接输出-1
|
|
|
exit(0);
|
|
|
}
|
|
|
return dist[n] <= k; //如果有k+1条边比我们现在这条边大,那么这个升级方案就是不合法的,反之就合法
|
|
|
}
|
|
|
int main() {
|
|
|
memset(h, -1, sizeof h);
|
|
|
cin >> n >> m >> k;
|
|
|
int a, b, c;
|
|
|
for (int i = 0; i < m; i++) {
|
|
|
cin >> a >> b >> c;
|
|
|
add(a, b, c), add(b, a, c);
|
|
|
}
|
|
|
/*这里二分的是直接面对答案设问:最少花费
|
|
|
依题意,最少花费其实是所有可能的路径中,第k+1条边的花费
|
|
|
如果某条路径不存在k+1条边(边数小于k+1),此时花费为0
|
|
|
同时,任意一条边的花费不会大于1e6
|
|
|
整理一下,这里二分枚举的值其实是0 ~ 1e6*/
|
|
|
int l = 0, r = 1e6;
|
|
|
while (l < r) {
|
|
|
int mid = (l + r) >> 1;
|
|
|
if (check(mid)) // check函数的意义:如果当前花费可以满足要求,那么尝试更小的花费
|
|
|
r = mid;
|
|
|
else
|
|
|
l = mid + 1;
|
|
|
}
|
|
|
printf("%d\n", l);
|
|
|
return 0;
|
|
|
}
|
|
|
```
|