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 的博客)

具体而言,每次增加一部分边/点,直到增加了所有边/点。增加后的最大费用任意流相较于增加前的最大费用任意流,只需要考虑三种东西的贡献:

  1. 新的增广路。

  2. 包含源点的正环。

  3. 包含汇点的正环。

当然上面三种东西,都必须涉及新加入的边

所以可以画出这样的图:

黑色是原本的图,红色是新加的点和边,两条绿色是可能的新增负环和增广路。

上面的增广路,要求 \(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;
}
posted @ 2024-03-29 20:17  FLY_lai  阅读(7)  评论(0编辑  收藏  举报