题解:洛谷 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 的最低价值:
- 拿着美元直接去加拿大,通过汇率得出一件 BM 只要 \(12\) 加拿大元;
- 拿着美元去英国,兑换为英镑后再去加拿大,得出一件 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
【核心思想】
-
问题分析:有 \(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\) 的最小乘积路径。
-
算法选择:
- SPFA 求最短路(乘积最小):将汇率视为边权,定义距离 \(dis[v]\) 为从 \(A\) 到 \(v\) 的最小乘积。松弛操作为:若 \(dis[v] > dis[u] \times w_{u \to v}\),则更新。这与普通最短路(加法)类似,只是运算从加法变为乘法,单位元从 0 变为 1(\(A\) 的初始距离为 \(V\))。
- 负环检测:若存在一个环,其汇率乘积 < 1,则在该环上不断兑换会使得价格无限降低,即 \(dis[B]\) 可趋于 0(无最小值)。SPFA 通过检测节点入队次数是否超过 \(N\) 来判断是否存在这样的“负环”(乘积环)。
-
关键步骤:
- 读入数据:\(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\)。
-
时间/空间复杂度:
- 时间复杂度:SPFA 平均 \(O(M)\),最坏 \(O(NM)\),\(N \le 2000, M \le 25000\),可接受。
- 空间复杂度:\(O(N+M)\)。
-
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
浙公网安备 33010602011771号