直接到最重要的地方。
题目
题目翻译理解
一共有 $N$ 个电线杆,其中有 $P$ 对电线杆之间可以连电缆,电话公司可以为 $K$ 根电缆免费,最终收费为需要收费的最长的那根电缆(费用等于长度)。问连接 $1$ 号电线杆和 $n$ 号电线杆最少费用是多少?
思路
二分
题目问的是需要支付的最长的那根费用的最小值是多少,是一个最小化最大值问题,可以考虑用二分。
假设长度 $x$ 满足条件,即在长度 $\gt x$ 的电缆免费、长度 $\le x$ 的电缆收费的情况下,存在一条从 $1$ 到 $n$ 的路径,其路径上的免费电缆的根数 $\le K$ 。那么,比 $x$ 大的 $x’$ 也能满足条件,因为能消耗免费额度的电缆变少了,也意味着没有顾虑地选的电缆变多了,也就更容易找到一条从 $1$ 到 $n$ 的路径,其路径上的免费电缆的根数 $\le K$ 。
相反地,如果长度 $x$ 不满足条件,那么长度 $\lt x$ 的 $x’$ 也不满足,即找不到一条从 $1$ 到 $n$ 的路径,其路径上的免费电缆的根数 $\le K$ 。
因此,这题的答案满足单调性,可以用二分。
最短路
二分的范围容易确定,就是在最短和最长的长度之间——当然,如果 $1$ 到 $n$ 本身就不连通的情况不能忘了。
注意到上面单调性讨论里的话,
能消耗免费额度的电缆变少了,也意味着没有顾虑地选的电缆变多了
这说明,当我们假设了一个答案 $m$ ,比 $m$ 大的需要消耗一个额度,而小于等于 $m$ 的不用,这提示我们,可以将大于 $m$ 的边看做边权为 $1$ ,小于等于 $m$ 的边权认为是 $0$ ,这样,问题就转换成了寻找从 $1$ 到 $n$ 的最短路,判断是否小于等于 $K$。如果判断为真,说明 $m$ 大了,要缩小。
Dijkstra
最基本的单源最短路就是 Dijkstra 了。不多说,直接上代码。
1 | def check_dijkstra(m: int) -> bool: |
0-1BFS
终于到这篇最重要的地方了。
0-1BFS 主要是在边权只有 $0$ 和 $1$ 的单源最短路问题上对 BFS 的优化。和普通的 BFS 相比,在代码上的差别就是在入队的方式上:
- 如果边权是 $1$,加到队尾;
- 如果边权是 $0$,加到队头。
1 | def check_01bfs(m: int) -> bool: |
尝试小小的理解和解释一下:
众所周知,广度优先搜索可以看做是水面涟漪一层层向外扩展的过程。结合图的最短路问题特别是边权都是 $1$ 和只有 $0/1$ 的问题来说,每一层的不同结点的路径都是相同的,相邻两层的结点一定是先访问内层的再访问外层的,内层的结点路径比外层小,同一层结点的访问没有先后顺序。那么,假设结点 $u$ 与 $v$ 相连边权是 $1$ ,当访问到 $u$ 的时候,$v$ 一定不是和 $u$ 在同一层,而 $u$ 的这一层可能还没有访问完,所以要比较后面访问 $v$ ,也就是把它加到队尾;但如果它们之间的边权是 $0$ ,说明 $v$ 和 $u$ 是在同一层,那么就要相对早一些访问 $v$ ,因此加到队头。
一个结点最多入队两次,一次是队头一次是队尾。用我这个水波涟漪的模型也好理解,因为同时在队列中的最多只有两层,$v$ 要么先因为一个边权为 $1$ 的入队尾然后因为一个边权为 $0$ 的入队头,然后访问到了之后,就确定了 $v$ 的最短路了呀。
完整代码
1 | # n 个电线杆,p 个可连接的边,电话公司可以为 k 根电缆的免费 |
其他
这题和 3681. 中位数 有异曲同工之处在于,它们都将权值转换、映射到了两个特殊值上,这题是边权变为 $0$ 和 $1$ 算最短路,另题是点权变为 $1$ 和 $-1$ 考虑中位数,我觉得还挺妙的。那道题的题解在这里。