1 条题解
-
0
题解:商人最大差价问题(图论 + 双向DP优化)
一、解题核心思路
本题的核心是找到从
1号城市到n号城市的路径中,一次买卖的最大差价(买入价最低,卖出价最高,且买入在卖出之前)。由于城市和道路数量极大(n≤1e5,m≤5e5),暴力遍历所有路径不可行,需通过 双向动态规划 优化:- 正向DP(
min_buy数组):计算从1号城市出发,到达每个城市u时,所有路径中能找到的最低买入价(包括u本身及之前经过的所有城市)。 - 反向DP(
max_sell数组):计算从每个城市u出发,到达n号城市时,所有路径中能找到的最高卖出价(包括u本身及之后经过的所有城市)。 - 最大差价计算:遍历所有城市
u,max_sell[u] - min_buy[u]的最大值即为答案(表示在u之前买入,u之后卖出的最大收益)。
二、关键逻辑推导
-
为什么可行?
商人的路径必然是1→...→u→...→n,买入点在1→u的路径中,卖出点在u→n的路径中。因此:min_buy[u]是1→u路径中最低的买入价(无论如何到达u)。max_sell[u]是u→n路径中最高的卖出价(无论如何从u到n)。- 两者的差值就是以
u为分界点的最大可能收益,遍历所有u即可得到全局最大值。
-
图的处理:
- 正向图:用于计算
min_buy(从1出发的路径)。 - 反向图:用于计算
max_sell(从n出发反向遍历,等价于正向路径u→n)。 - 双向道路
z=2转化为两条单向道路(正向和反向各一条)。
- 正向图:用于计算
三、具体解题步骤
步骤1:构建正向图和反向图
- 正向图
adj:存储每个城市的直接后继(道路通行方向)。 - 反向图
radj:存储每个城市的直接前驱(道路通行方向反转)。 - 双向道路
z=2需在两个图中各添加两条反向边。
步骤2:计算
min_buy数组(正向DP)- 初始化:
min_buy[1] = 1号城市的价格,其余为无穷大(1e9)。 - 用队列实现 SPFA-like 遍历:从
1号城市出发,遍历所有可达城市,更新min_buy[v] = min(min_buy[u], 城市v的价格)(u是v的前驱)。 - 核心:每次发现更低的买入价时,更新并重新入队,确保所有路径都被考虑。
步骤3:计算
max_sell数组(反向DP)- 初始化:
max_sell[n] = n号城市的价格,其余为0。 - 用队列实现 SPFA-like 遍历:从
n号城市出发,遍历反向图的所有可达城市,更新max_sell[u] = max(城市u的价格, max_sell[v])(v是u在反向图中的前驱,即正向图中u的后继)。 - 核心:每次发现更高的卖出价时,更新并重新入队,确保所有路径都被考虑。
步骤4:计算最大差价
- 遍历所有城市
u,计算max_sell[u] - min_buy[u],取最大值。若最大值≤0,输出0(无收益)。
四、完整代码实现
#include <iostream> #include <vector> #include <queue> #include <climits> #include <algorithm> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<int> price(n); for (int i = 0; i < n; ++i) { cin >> price[i]; } // 构建正向图adj和反向图radj(1-based城市编号) vector<vector<int>> adj(n + 1), radj(n + 1); for (int i = 0; i < m; ++i) { int x, y, z; cin >> x >> y >> z; // 正向边:x→y adj[x].push_back(y); radj[y].push_back(x); // 双向边额外添加y→x if (z == 2) { adj[y].push_back(x); radj[x].push_back(y); } } // 步骤1:计算min_buy[u]:1→u路径中最低价格(包括u) vector<int> min_buy(n + 1, INT_MAX); min_buy[1] = price[0]; // 城市1对应price[0] queue<int> q; q.push(1); while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) { // 到v的最低价格 = 到u的最低价格 和 v本身价格的最小值 int current_min = min(min_buy[u], price[v - 1]); if (current_min < min_buy[v]) { min_buy[v] = current_min; q.push(v); } } } // 步骤2:计算max_sell[u]:u→n路径中最高价格(包括u) vector<int> max_sell(n + 1, 0); max_sell[n] = price[n - 1]; // 城市n对应price[n-1] q.push(n); while (!q.empty()) { int v = q.front(); q.pop(); for (int u : radj[v]) { // u的最高卖出价 = u本身价格 和 v的最高卖出价的最大值 int current_max = max(price[u - 1], max_sell[v]); if (current_max > max_sell[u]) { max_sell[u] = current_max; q.push(u); } } } // 步骤3:计算最大差价 int ans = 0; for (int u = 1; u <= n; ++u) { ans = max(ans, max_sell[u] - min_buy[u]); } cout << ans << endl; return 0; }五、代码解释
-
图的构建:
- 正向图
adj用于正向遍历(从1出发),反向图radj用于反向遍历(从n出发)。 - 双向道路
z=2转化为两条单向边,确保遍历覆盖所有可能路径。
- 正向图
-
正向DP(
min_buy):- 初始时只有
1号城市的价格已知,其他为无穷大。 - 遍历每个城市的后继,更新后继的最低买入价(继承前驱的最低价格,或取自身价格)。
- 队列确保所有可能的路径都被处理,即使存在环(价格范围仅
1~100,不会无限更新)。
- 初始时只有
-
反向DP(
max_sell):- 初始时只有
n号城市的价格已知,其他为0。 - 遍历反向图的前驱(即正向图的后继),更新前驱的最高卖出价(继承后继的最高价格,或取自身价格)。
- 初始时只有
-
最大差价计算:
- 遍历所有城市,取
max_sell[u] - min_buy[u]的最大值,确保收益非负(无收益时输出0)。
- 遍历所有城市,取
六、复杂度分析
- 时间复杂度:
O(m)。由于价格范围仅1~100,每个城市最多被更新100次(最低买入价最多降100次,最高卖出价最多升100次),总操作数约5e7(m=5e5),完全适配数据规模。 - 空间复杂度:
O(n+m)。存储两个邻接表和两个DP数组,空间开销可控。
该算法通过双向DP将复杂的路径遍历转化为高效的图遍历,完美解决了大规模图中的最大差价问题。
- 正向DP(
- 1
信息
- ID
- 5413
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者