给学弟办一场模拟赛,结果差点把自己给讲懵了……
于是决定记录下来,顺便理一理思路
说实话,这一题挺有价值的
题面
在郊区有 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 条电缆,由电话公司免费提供升级服务。农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。求至少用多少钱能完成升级。
这一题同时也是 POJ3662,原题链接戳这里
初步思考——证明答案的单调性
选择的 K K K 条边一定是花费最多的 K K K 条边,这是十分显然的。
接着我们要明确一点,这道题的答案(即最终花费)是具有单调性的。
怎么证明呢?
不妨考虑一下,如果选择一条边 e e e 的权值作为最终花费是不可行的,那它应该满足什么条件呢?
如果从图中任意选取一条从 1 1 1 到 N N N 的路径,记这个路径上所有边组成的集合为 E E E。如果 e ∈ E e \in E e∈E 且 e e e 是 E E E 中花费第 K + 2 ∼ N − 1 K + 2 \sim N-1 K+2∼N−1 大的边,那么 e e e 的权值就一定不可以作为最终的答案(注意要对任何路径都成立)。因为即便你指定了 E E E 中权值前 K K K 大的为免费的边, e e e 依然不可能是剩余边中权值最大的边,总会有边比它的权值大。
那么,如果一条边 e 1 e_1 e1 的值 v 1 v_1 v1 不可能作为最终花费,那么任何比它小的边 e 2 e_2 e2 的权值 v 2 v_2 v2 都不可能作为最终答案。
B e c a u s e Because Because, e 1 e1 e1 已经不可能在任意的 E E E 中权值排名前 K + 1 K + 1 K+1 大了,比 v 1 v_1 v1 的权值还要小的 e 2 e_2 e2 更加不可能排名前 K + 1 K + 1 K+1 大,也就不可能成为剩余的边中的最大花费。
至此,答案的单调性得证。
既然知道了答案是具有单调性的,那么我们就可以二分答案了。
转化——判定性问题
经过一系列的思考,我们将求解问题转化成了一个更易于求解的判定问题:
是否存在一种升级方案,使得剩余的电缆中,升级价格最贵的那条电缆的花费不超过 v a l val val。
先给出二分的代码:(浅显易懂)
l = 0; r = maxw;
while (l < r) { // 答案具有单调性,所以进行二分
mid = (l + r) >> 1;
if (k < check(mid)) l = mid + 1; // 必须要选择的边数超过了k
else r = mid;
}
printf("%d\n", l);
判定——01赋值法
若我们经过了一条花费大于 v a l val val 的电缆,则肯定要让电话公司对其进行升级;若我们经过了一条花费小于等于 v a l val val 的电缆,则不需要对其升级,把它算到路径上的 K K K 条电缆中即可。
于是我们可以将花费大于 v a l val val 的电缆看作是一条长度为 1 1 1 的边,将花费小于等于 v a l val val 的电缆看作是一条长度为 0 0 0 的边。用这种权值方式跑一遍 Dijkstra。若在这种计算方式下, 1 1 1 到 N N N 的最短路不超过 K K K,则存在一种升级方案,使得总花费不超过 v a l val val;反之则不存在一种升级方案,使得总花费不超过 v a l val val。
以下是判定合法性的代码:(简洁明了)
inline int check(int ans) {
fill(dis + 2, dis + 1 + n, 0x3f3f3f3f);
fill(vis + 1, vis + 1 + n, 0);
dis[1] = 0; // 保证第一次是取1号为起点
for (int i = 1; i <= n; i++) {
minn = 0x3f3f3f3f;
beg = 0;
for (int j = 1; j <= n; j++) {
if (!vis[j] && dis[j] < minn) { //!vis[j]: 还没有被作为起点过
minn = dis[j];
beg = j;
}
}
if (beg == 0) // 如果没有找到起点
break;
vis[beg] = 1;
for (int j = head[beg]; j; j = e[j].next) {
int y = e[j].to;
int p = (e[j].w > ans ? 1 : 0); // 重新计算边权
if (!vis[y] && p + dis[beg] < dis[y]) {
dis[y] = p + dis[beg];
}
}
}
return dis[n];
}
时间复杂度 O ( ( N + P ) log M a x w ) O((N+P) \log Maxw) O((N+P)logMaxw),其中 M a x w Maxw Maxw 为花费最大的电缆的花费。
结语
主要是证明答案的单调性挺难给出严谨证明的……(或许是我太菜了)当然,意会一下其实也能知道
01赋值法那一块比较考察思想
其他就没什么了