[最短路] aw1126. 最小花费(单源最短路建图+知识理解+代码细节+好题)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[最短路] aw1126. 最小花费(单源最短路建图+知识理解+代码细节+好题)相关的知识,希望对你有一定的参考价值。
1. 题目来源
链接:1126. 最小花费
相关链接:
2. 题目解析
很不错的一道题。
可以将从起点 A
到终点 B
的最优转账路径花费进行如下表示:
100
=
A
∗
w
1
∗
w
2
∗
w
3
+
.
.
.
100=A*w_1*w_2*w_3+...
100=A∗w1∗w2∗w3+...
则问题转化为,求 A
最小,则等价于求
w
1
∗
w
2
∗
w
3
+
.
.
.
w_1*w_2*w_3+...
w1∗w2∗w3+... 最大。即,求乘积的最大值。
在以往最短路问题中,运用几大最短路算法,可以求和的最小值。在本题,需要做简单转化,也可使用最短路模型求解乘积的最大值:
- w i w_i wi 是在 [ 0 , 1 ] [0, 1] [0,1] 之间的数,对 w 1 ∗ w 2 ∗ w 3 + . . . w_1*w_2*w_3+... w1∗w2∗w3+... 取 l o g log log,则 l o g ( w 1 ∗ w 2 ∗ w 3 + . . . ) = l o g w 1 + l o g w 2 + l o g w 3 + . . . log(w_1*w_2*w_3+...)=logw_1+logw_2+logw_3+... log(w1∗w2∗w3+...)=logw1+logw2+logw3+... 此时,由于 l o g log log 函数单调递增,等价于求 l o g w 1 + l o g w 2 + l o g w 3 + . . . logw_1+logw_2+logw_3+... logw1+logw2+logw3+... 的最大值。
- 每个 l o g w i ≤ 0 logw_i \\le 0 logwi≤0,求负数的最大值等价于求正数的最小值。则可以对其取反,然后每个数都为正,求正权边的最小值即可。
- 故,本题有
w
i
w_i
wi 是在
[
0
,
1
]
[0, 1]
[0,1] 之间的数(左区间一般不能取到 0),这个范围限制,才导致全为正权边,可以使用
dijkstra()
算法进行求解。否则,只能使用spfa()
。
实现细节:
- 虽然分析是要将边权取 l o g log log、取反,再进行最短路求解。
- 但实际上不需要这样做,现在求乘积最大值,边权也是设到了
[
0
,
1
]
[0, 1]
[0,1] 之间。就将最短路算法里的加法替代成乘法即可。 这也是算法中的常见操作,并且需要将
dist[S] = 1
,然后将更新操作的dist[j]=dist[t]+w
改变为dist[j]=dist[t]*w
就行了。 - 这样操作就相当的方便了。以往的选取一条边,距离需要加和。现在就将这个加和改成了乘积。 以前
0x3f3f3f3f
是不可达的点,现在 0 是不可达的点。所以dist
数组也不必初始化了。最终dist[T]
放的其实就是起点到终点所有路径中乘积的最大值。 - 并且在重边处理上,需要取权值最大的一条边,这条边的手续费就少。在更新时也是,取
max
而不是取min
,保证乘积最大!
考虑清楚,细节实现!
简单总结:
- 加法最小值:
- 无负权:
dijkstra()
、spfa
- 有负权:
spfa
- 无负权:
- 加法最大值:
- 不会严格证明,不知道
spfa
能否搞定
- 不会严格证明,不知道
- 乘法最小值(关于乘法,边权只能是全为正,不能为负。一旦为负,最大值、最小值成一个负数就立马颠倒过来了,十分难求,需要维护更多的信息。一般来讲,乘法求最值,边权都是正数):
-
w
i
≥
1
w_i\\ge1
wi≥1,等价于无负权,取
l
o
g
log
log 后边权为正。
dijkstra()
、spfa
-
w
i
≥
0
w_i\\ge0
wi≥0,等价于有负权,
spfa
-
w
i
≥
1
w_i\\ge1
wi≥1,等价于无负权,取
l
o
g
log
log 后边权为正。
- 乘法最大值:
-
0
≤
w
i
≤
1
0 \\le w_i\\le1
0≤wi≤1,取
l
o
g
log
log 后边权为负,求负数最大等于求正数最小,正权图。
dijkstra()
、spfa
-
w
i
≥
0
w_i\\ge 0
wi≥0,存在负权,
spfa
-
0
≤
w
i
≤
1
0 \\le w_i\\le1
0≤wi≤1,取
l
o
g
log
log 后边权为负,求负数最大等于求正数最小,正权图。
- 在此, w i w_i wi 是否能够取 0 值得商榷。
小知识点:
- 如何对double型变量进行memset获得极大值或极小值
- 这个是重要的知识点,不要以为初始化
memset(dist, 0x3f, sizeof dist)
是将double
类型的dist
初始化为极大值。实际上它和 0 差不多。 - 一般来讲可以循环初始化。或者采用链接中的方法。
- 极大值的时候,可以选择0x7f,如果觉得这个数字过于夸张,可以选择0x42或者0x43。同样,想清最小值的时候,可以选择0xfe或0xc2。
时间复杂度: O ( n 2 ) O(n^2) O(n2),由算法决定
空间复杂度: O ( n ) O(n) O(n)
朴素版 dijkstra
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2005;
int n, m, S, T;
double g[N][N];
double dist[N];
bool st[N];
void dijkstra() {
dist[S] = 1;
for (int i = 0; i < n; i ++ ) {
int t = -1;
for (int j = 1; j <= n; j ++ )
if (!st[j] && (t == -1 || dist[t] < dist[j])) // 这个是最大值
t = j;
st[t] = true;
// 取最大值
for (int j = 1; j <= n; j ++ ) dist[j] = max(dist[j], dist[t] * g[t][j]);
}
}
int main() {
scanf("%d%d", &n, &m);
while (m -- ) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
double t = (100.0 - c) / 100; // 变为 0~1 的小数
g[a][b] = g[b][a] = max(g[a][b], t); // 建图,乘积最大值,取重边较大的一个
}
scanf("%d%d", &S, &T);
dijkstra();
printf("%.8lf\\n", 100.0 / dist[T]);
return 0;
}
spfa+循环队列:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2005, M = 1e5*2;
int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
double dist[N];
bool st[N];
int q[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void spfa() {
memset(dist, 0, sizeof dist);
int hh = 0, tt = 1;
q[0] = S, dist[S] = 1;
while (hh != tt) {
auto t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
double cost = (100.0 - w[i]) / 100;
if (dist[j] < dist[t] * cost) { // 注意这里的符号,松弛条件改变
dist[j] = dist[t] * cost;
if (!st[j]) {
st[j] = true;
q[tt ++] = j;
if (tt == N) tt = 0;
}
}
}
}
}
int main() <以上是关于[最短路] aw1126. 最小花费(单源最短路建图+知识理解+代码细节+好题)的主要内容,如果未能解决你的问题,请参考以下文章
[最短路] aw1127. 香甜的黄油(单源最短路建图+模板题)
[最短路] aw920. 最优乘车(单源最短路建图+bfs最短路模型+知识理解+好题)
[最短路] aw903. 昂贵的聘礼(单源最短路建图+超级源点+知识理解+好题)
[最短路] aw1129. 热浪(单源最短路建图+spfa循环队列+模板题)