[最短路] 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=Aw1w2w3+...
则问题转化为,求 A 最小,则等价于求 w 1 ∗ w 2 ∗ w 3 + . . . w_1*w_2*w_3+... w1w2w3+... 最大。即,求乘积的最大值。

在以往最短路问题中,运用几大最短路算法,可以求和的最小值。在本题,需要做简单转化,也可使用最短路模型求解乘积的最大值:

  • w i w_i wi 是在 [ 0 , 1 ] [0, 1] [0,1] 之间的数,对 w 1 ∗ w 2 ∗ w 3 + . . . w_1*w_2*w_3+... w1w2w3+... 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(w1w2w3+...)=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 logwi0求负数的最大值等价于求正数的最小值。则可以对其取反,然后每个数都为正,求正权边的最小值即可。
  • 故,本题有 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 wi1,等价于无负权,取 l o g log log 后边权为正。dijkstra()spfa
    • w i ≥ 0 w_i\\ge0 wi0,等价于有负权,spfa
  • 乘法最大值:
    • 0 ≤ w i ≤ 1 0 \\le w_i\\le1 0wi1,取 l o g log log 后边权为负,求负数最大等于求正数最小,正权图。dijkstra()spfa
    • w i ≥ 0 w_i\\ge 0 wi0,存在负权,spfa
  • 在此, 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循环队列+模板题)

[最短路] aw1128. 信使(单源最短路建图+Floyd算法+最短路理解+模板题)

[最短路] aw3772. 更新线路(bfs最短路模型+单源最短路的扩展应用+最短路计数+aw周赛008_3)