题解:洛谷 P3001 Big Macs Around the World

【题目来源】

洛谷:P3001 [USACO10DEC] Big Macs Around the World G

【题目描述】

Bessie 正在学习她最喜欢的科目宏观经济学,作为她最后一门学科,她将对世界各种货币的汇率进行研究。

为了让她的演讲更加生动,她会展示一个叫做 BM 的商品在全世界的相对价格。举个例子,Bessie 会通过其他国家的汇率去找到一件 BM 在一个国家的最小价值。

  • 一件 BM 在美国值 \(60\) 美元;
  • 美元与加拿大元的汇率为 \(1\) 美元换 \(0.2\) 加拿大元(\(1:0.2\))。
  • 美元与英镑的汇率为 \(1\) 美元换 \(5\) 英镑(\(1:5\))。
  • 英镑与加拿大元的汇率为 \(1\) 英镑换 \(0.5\) 加拿大元(\(1:0.5\))。
  • 加拿大元与美元的汇率是 \(5\) 美元换一加拿大元(\(5:1\)),Bessie 有两种方法通过货币兑换在加拿大这个国家找到一件 BM 的最低价值:
  1. 拿着美元直接去加拿大,通过汇率得出一件 BM 只要 \(12\) 加拿大元;
  2. 拿着美元去英国,兑换为英镑后再去加拿大,得出一件 BM 要 \(150\) 加拿大元。

Bessie 会选择前一种方案因为她更乐意为在加拿大买一件 BM 支付 \(12\) 加元而不是 \(150\) 加元。

Bessie 有 \(N(1\leq N\leq 2000)\) 个国家的信息和 \(M(1\leq M\leq25000)\) 种汇率,在 \(i,j\) 国间的汇率表示为 \(e_{ij}(0.1\leq e_{ij}\leq 10)\)

给你一个值 \(V(1\leq V\leq 10^{12})\)\(V\) 不一定是一个整数。\(V\) 是 BM 在起始国家 A 的价格,帮助 Bessie 寻找到在 B 国 BM 最低的价格,如果不存在,则输出 \(0\)

据保证答案小于 \(10^{15}\),也保证所有国家都可以通过汇率将钱币转为别的国家的。

【输入】

\(1\) 行:五个数:\(N,M,V,A,B\),分别一个空格隔开。

\(2\)\(M+1\) 行:三个数 \(i,j,e_{ij}\),分别一个空格隔开。

【输出】

一行:BM 在 B 国的最低价格,精确到 \(10^{-6}\)。如果没有最小值,输出 \(0\)

【输入样例】

3 4 60 1 2
1 2 0.2
1 3 5
3 2 0.5
2 1 5

【输出样例】

12.00

【核心思想】

  1. 问题分析:有 \(N\) 个国家,\(M\) 种汇率(有向边 \(i \to j\) 的汇率为 \(e_{ij}\))。商品在起始国 \(A\) 的价格为 \(V\),通过不断兑换货币,商品价格会乘以汇率(例如从 \(A\)\(B\)\(V \times e_{A \to X} \times e_{X \to Y} \times \cdots\))。目标是求在目标国 \(B\) 的最低可能价格。若存在一个兑换环使得总乘积 < 1(即不断兑换可降低价格至任意小),则无最小值,输出 0。本质是带权有向图的最短路问题,边权为汇率(正实数),求从 \(A\)\(B\) 的最小乘积路径

  2. 算法选择

    • SPFA 求最短路(乘积最小):将汇率视为边权,定义距离 \(dis[v]\) 为从 \(A\)\(v\) 的最小乘积。松弛操作为:若 \(dis[v] > dis[u] \times w_{u \to v}\),则更新。这与普通最短路(加法)类似,只是运算从加法变为乘法,单位元从 0 变为 1(\(A\) 的初始距离为 \(V\))。
    • 负环检测:若存在一个环,其汇率乘积 < 1,则在该环上不断兑换会使得价格无限降低,即 \(dis[B]\) 可趋于 0(无最小值)。SPFA 通过检测节点入队次数是否超过 \(N\) 来判断是否存在这样的“负环”(乘积环)。
  3. 关键步骤

    • 读入数据\(N, M, V, A, B\),以及 \(M\) 条有向边 \((u, v, e)\)
    • SPFA 初始化\(dis[A] = V\),其余 \(dis[i] = \infty\)。队列初始包含 \(A\)
    • 松弛操作:从队列取出 \(u\),遍历所有出边 \(u \to v\) 权值 \(w\),若 \(dis[v] > dis[u] \times w\),则更新 \(dis[v]\),并将 \(v\) 入队,同时增加 \(cnt[v]\) 入队计数。
    • 检测负环:若某节点入队次数超过 \(N\),说明存在可无限降低价格的兑换环,标记并退出。
    • 输出:若无负环,输出 \(dis[B]\)(保留 6 位小数);否则输出 \(0\)
  4. 时间/空间复杂度

    • 时间复杂度:SPFA 平均 \(O(M)\),最坏 \(O(NM)\)\(N \le 2000, M \le 25000\),可接受。
    • 空间复杂度:\(O(N+M)\)
  5. SPFA + 乘积最短路的

    • 对数转换(可选):若用加法最短路,可对汇率取对数 \(\log(e)\),将乘积转化为加法(\(V \times e_1 \times e_2 = V \times e^{\ln e_1 + \ln e_2}\))。但直接使用乘法更直观,SPFA 同样适用。
    • 负环的乘积特征:环的汇率乘积 < 1 等价于 \(\sum \ln(e) < 0\),即对数图中的负环。SPFA 的入队次数检测通用。
    • 精度处理:使用 long double 保证浮点精度,满足 \(10^{-6}\) 的输出要求。
    • 适用场景:适用于货币兑换、单位转换等可乘积路径问题,求最小或最大乘积,特别是存在“套利”机会(乘积 < 1 的环)需检测无界情况。

【算法标签】

普及 #SPFA

【代码详解】

#include <bits/stdc++.h>
using namespace std;

#define double long double          // 使用更高精度,避免浮点误差

typedef pair<int, double> PII;      // 邻接表元素:目标国家,汇率

const int N = 2005;

int n, m, A, B;                     // 国家数,汇率数,起始国,目标国
double V;                           // BM在起始国的价格

double dis[N];                      // 到每个国家的最低价格(乘积最小值)
vector<PII> g[N];                   // 邻接表存图
bool flag;                          // 标记是否存在负环(此处指可无限降低价格的环)
int cnt[N];                         // 记录每个节点入队次数,用于检测负环

// SPFA算法求从A到所有国家的最低价格(等价于求最短路径,边权为汇率乘积)
void spfa()
{
    // 初始化所有距离为极大值
    for (int i = 1; i <= n; i++)
        dis[i] = 1e18;

    dis[A] = V;                     // 起始国价格为V
    queue<int> q;
    q.push(A);

    while (!q.empty())
    {
        int u = q.front();
        q.pop();

        // 遍历从u出发的所有汇率边
        for (auto [v, w] : g[u])
        {
            // 如果通过u到达v的价格比当前记录更低,则更新
            if (dis[v] > dis[u] * w)
            {
                dis[v] = dis[u] * w;
                q.push(v);
                cnt[v]++;           // 入队次数加一

                // 如果某个节点入队超过n次,说明存在负环(可无限降低价格)
                if (cnt[v] > n)
                {
                    flag = true;    // 标记存在负环,即价格可以无限降低,无最小值
                    return;
                }
            }
        }
    }
}

int main()
{
    cin >> n >> m >> V >> A >> B;

    // 读入所有汇率边
    while (m--)
    {
        int u, v;
        double w;
        cin >> u >> v >> w;
        g[u].push_back({v, w});     // 有向边:u -> v 汇率为w
    }

    spfa();

    // 如果存在负环(价格可无限降低),则没有最小值,输出0
    if (flag)
        cout << 0 << endl;
    else
        // 输出目标国B的最低价格,保留6位小数
        printf("%.6Lf\n", dis[B]);

    return 0;
}

【运行结果】

3 4 60 1 2
1 2 0.2
1 3 5
3 2 0.5
2 1 5
12.000000
posted @ 2026-08-31 09:26  团爸讲算法  阅读(7)  评论(0)    收藏  举报