CF865D Buy Low Sell High
传送门
题意:已知未来 \(n\) 天的股价 \(c_i\),每天可以买入一支或者卖出一支,求 \(n\) 天后利润总额最大是多少。
算法:模拟费用流。
【费用流模型】
把每一天抽象为一个结点:\(d_1\sim d_n\)。
\(S\rightarrow d_1\sim d_n\),容量 \(1\) 费用 \(-c_i\)。
\(d_1\sim d_n\rightarrow T\),容量 \(1\) 费用 \(c_i\)。
\(d_i\rightarrow d_{i+1}\),容量 \(+\infty\) 费用 \(0\)。
求最大费用任意流,意思就是不限制流量但是要求费用最大。
这种任意流有一个特点:就是费用为负数的增广路一定不会流。(很显然吧)
【模拟费用流】
直接费用流显然不行,传统的模拟费用流(整体考虑)不太好维护(因为 \(d_1\sim d_n\) 这一连串的边较难维护状态)。
在这里提出一种新的模型:"增量-最大费用任意流模型"。(名字取自 command_block 的博客)
具体而言,每次增加一部分边/点,直到增加了所有边/点。增加后的最大费用任意流相较于增加前的最大费用任意流,只需要考虑三种东西的贡献:
-
新的增广路。
-
包含源点的正环。
-
包含汇点的正环。
当然上面三种东西,都必须涉及新加入的边。
所以可以画出这样的图:
黑色是原本的图,红色是新加的点和边,两条绿色是可能的新增负环和增广路。
上面的增广路,要求 \(S\rightarrow d_i\) 的边没用过;下面的正环,要求 \(d_i\rightarrow T\) 的边用过。
注意这里不考虑 \(d_i\rightarrow d_{i+1}\) 的边
可以用两个 set 维护。因为新增的 \(d_{new}\rightarrow T\) 的边容量只有 \(1\),所以要从增广路和正环中取一个权值较大的走。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n;
int c[300005];
multiset<int> s1, s2; //s1是上层没用过的边,s2是下层用过的边
long long sum = 0;
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> c[i];
s1.insert(c[1]);
for (int i = 2; i <= n; i++) {
int a = -1, b = -1;
if (!s1.empty())
a = *s1.begin();
if (!s2.empty())
b = *s2.begin();
if (a != -1 && (a <= b || b == -1)) {
if (c[i] >= a) {
sum += c[i] - a;
s2.insert(c[i]);
s1.erase(s1.find(a));
}
else
s1.insert(c[i]);
}
else if (b != -1 && (b <= a || a == -1)) {
if (c[i] >= b) {
sum += c[i] - b;
s1.insert(b);
s2.erase(s2.find(b));
s2.insert(c[i]);
}
else
s1.insert(c[i]);
}
}
cout << sum << endl;
return 0;
}