题解 P1073 【最优贸易】
fy1234567ok · · 题解
分层图状态转移 + SPFA
其实此题可以不用强连通分量缩点,还有更优美的解法,只需40行代码。
主要思想是类似“分层图”,或者类似“DAG”(有向无环图)的状态转移思想,特别是针对这种状态量相互影响的问题,分层图思想很实用。
分析
读完这道题,可以发现这样的事实:
-
你可以在图上任意走动
-
最终答案只与你的买入与卖出价格有关(我们就把买入卖出价值作为边权)
-
如果你买入了一个水晶球,你是没有不卖它的道理的(显然咯,买了不卖血亏...)
暴力算法不难得出:
我只关心我在哪里买了这个水晶球,在哪里把它卖出去,并且,我能否从起点走到我的买入点,从买入点走到卖出点,然后在走到
因此,先枚举两个点再bfs检查能否到达,然后更新答案。
而此题的难点在与你如何知道你是否能够到达买入,卖出,终点(即上两行 并且 后面我说的话),和你能否把所有可能的情况考虑在内。
分层图可以很好的解决这个问题。
由于可以任意走动,所以我们可以建一张图,令图上的边全都是
考虑某个点
那么:
当我们进行买入操作,我们就建立一条有向边转移到一张新图上,边的大小为 指向点 指向第二层的点 $i{1}$_ 。而这张新图就是我们的第二层图。
它表示:假如我选择走了这条边,就是我在这个点买了这个水晶球,我不会反悔,并且我接下来考虑在某个点卖它。
当我们进行卖出操作,我们建立一条有向边转移到第三层图上,边的大小为 指向点 指向第三层的点 $i{2}$_ 。
它表示:假如我选择走了这条边,就是我在这个点卖了这个水晶球,我不会反悔,并且我接下来考虑走向终点。
注:不能指向
i 下一层到达的点,因为这样意思就变成了:我在这个点买入了水晶球,并且我一定从这个点走出去。多了一层意思就不一样了
可以发现,从第一层图走到第二层图走到第三层图走到终点,这就是一个合法的决策。
对于任何一种决策都可以抽象为我从
所以分层图把所有合法的决策都考虑到了。而我们要求的最大收益就正好对应了图上的从
注:当然也可以把边权都改成负的求个最短路(因为不存在负环也是可以的)
最后解释一下为什么我们要分层:
因为当你分了层,你就可以从还未买入这个状态,转移到已经买入准备卖出这个状态,然后在转移到从卖出点走向终点的状态。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖,符合了题目的要求
而我们最终的答案,就是求从第一层图的
到此,这道题就解完了。
UPDATE 2020.10.6
其实2年前我已经退役,抱歉鸽了这么久才更新。感谢在评论中帮我指出错误,提出建议的大佬们。这次更新修改了建模,符号加入了LaTeX, 优化了代码。Hack数据已经可以通过了。如果发现了其他问题,欢迎在评论区中讨论,感谢各位的支持。
代码如下
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, m, d[maxn*3], inq[maxn*3];
vector<pair<int, int>> G[maxn*3];
#define t(x,i) (x+i*n) // t(x,i) 表示第i层的x
// 建立x->y边的函数, 不用加 make_pair是 C++11特性
#define add(x, y) G[t(x,0)].push_back({t(y,0), 0}), G[t(x,1)].push_back({t(y,1),0}), G[t(x,2)].push_back({t(y,2),0})
void spfa(int s) {
for(int i = 1;i <= n*3;i++) d[i] = INT_MIN; // 这里n*3别漏了, INT_MIN 是C++内置最小值常量
d[s] = 0;
queue<int> Q; inq[s] = true; Q.push(s);
while(!Q.empty()) {
int x = Q.front(); Q.pop(); inq[x] = false;
for(auto [v, len] : G[x]) // C++17 特性, 等价于 int v = G[x][i].first, len = G[x][i].second;
if(d[v] < d[x] + len) {
d[v] = d[x] + len;
if(!inq[v]) { Q.push(v); inq[v] = true; }
}
}
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(0); // 加速cin, cout
cin >> n >> m;
for(int i = 1, v;i <= n; ++i) {
cin >> v;
G[t(i,0)].push_back({t(i,1), -v});
G[t(i,1)].push_back({t(i,2), v});
}
for(int i = 1,x,y,z;i <= m; ++i) {
cin >> x >> y >> z; add(x, y);
if(z == 2) add(y, x);
}
spfa(t(1,0));
cout << d[t(n,2)] << endl;
return 0;
}