@loj - 3158@「NOI2019」序列

目录


@description@

给定两个长度为 n 的正整数序列 {ai} 与 {bi},序列的下标为 1,2,…,n。
现在你需要分别对两个序列各指定恰好 K 个下标,要求至少有 L 个下标在两个序列中都被指定,使得这 2K 个下标在序列中对应的元素的总和最大。

形式化地说,你需要确定两个长度为 K 的序列 {ci},{di},其中 1≤c1<c2<…<cK≤n,1≤d1<d2<…<dK≤n,并要求 |{c1,c2,…,cK}∩{d1,d2,…,dK}|≥L。目标是最大化:

\[\sum_{i=1}^{K}(a_{c_i} + b_{d_i}) \]

@solution@

首先 O(n^3) 的 dp 人人都会。然后据说可以优化出来

考虑换思路,使用费用流建模:
将点 i 拆成 i -> i',费用 0 容量 1;
然后连 s1 -> i 费用 ai 容量 1,连 i' -> t1 费用 bi 容量 1。
再连 s2 -> i' 费用 0 容量 1,连 i -> t2 费用 0 容量 1。
最后连 s -> s1 费用 0 容量 K,连 s -> s2 费用 0 容量 K - L;连 t1 -> t 费用 0 容量 K,连 t2 -> t 费用 0 容量 K - L。
(好像跟网上的一般建图方法不大一样来着。。。不过无所谓,一样可以优化)

这样跑最小费用流(或者最小费用最大流也可)就可以有 40 分的好成绩(靠,我当时为什么没想到)

考虑优化。注意使用连续最短路算法时,我们总会先选择前 K 个 ai + bi 最大的 i,并发送流量 s -> s1 -> i -> i' -> t1 -> t。
然后我们会跑 K - L 次最短路。每次最短路总会是以下几种效果之一:
1)ai + bi -> ai + bj。
2)ai + bi -> aj + bi。
3)ai + bi + aj + bj -> ai + bj + ak + bk。
4)ai + bi -> aj + bk。

可以自己手算一下边界情况(如 K - L = 1)验证一下,或者手画一个网络流跑一跑。

然后就可以用可删除堆 O(nlog n) 乱做。

@accepted code@

#include <queue>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, int> pli;
#define fi first
#define se second
#define mp make_pair

const int MAXN = 200000;
const ll INF = (1LL << 60);

int tag[MAXN + 5];
struct heap{
	int type; priority_queue<pli>q; heap() {}
	heap(int _t) : type(_t) {}
	void maintain() {
		while( !q.empty() && tag[q.top().se] != type && (type == 3 || tag[q.top().se] != 0) )
			q.pop();
	}
	bool empty() {maintain(); return q.empty();}
	pli top() {maintain(); return empty() ? make_pair(-INF, -1) : q.top();}
	void push(pli x) {q.push(x); maintain();}
	void pop() {q.pop(); maintain();}
};

int read() {
	int x = 0, ch = getchar();
	while( ch > '9' || ch < '0' ) ch = getchar();
	while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
	return x;
}

pii a[MAXN + 5];
bool cmp_ab(const pii &x, const pii &y) {
	return x.fi + x.se > y.fi + y.se;
}
void solve() {
	int n = read(), K = read(), L = read();
	for(int i=1;i<=n;i++) a[i].fi = read();
	for(int i=1;i<=n;i++) a[i].se = read();
	
	ll ans = 0; heap hn1(3), hn2(3), hn3(3), hx1(0), hx2(1), hx3(2);
	sort(a + 1, a + n + 1, cmp_ab);
	for(int i=1;i<=K;i++) {
		ans += a[i].fi + a[i].se;
		
		tag[i] = 3;
		hn1.push(mp(-(a[i].fi + a[i].se), i));
		hn2.push(mp(-a[i].fi, i)), hn3.push(mp(-a[i].se, i));
	}
	for(int i=K+1;i<=n;i++) {
		tag[i] = 0;
		hx1.push(mp(a[i].fi + a[i].se, i));
		hx2.push(mp(a[i].fi, i)), hx3.push(mp(a[i].se, i));
	}
	for(int i=1;i<=K-L;i++) {
		pli mnab = hn1.top(), mna = hn2.top(), mnb = hn3.top();
		pli mxab = hx1.top(), mxa = hx2.top(), mxb = hx3.top();
		
		ll da = mxa.fi + mna.fi, db = mxb.fi + mnb.fi;
		ll dc = mxab.fi + mna.fi + mnb.fi, dd = mxa.fi + mxb.fi + mnab.fi;
		if( dd > dc && dd > db && dd > da && dd > 0 ) {
			ans += dd;
			tag[mxb.se] = 1, hx2.push(mp(a[mxb.se].fi, mxb.se));
			tag[mxa.se] = 2, hx3.push(mp(a[mxa.se].se, mxa.se));
			tag[mnab.se] = 0;
			hx1.push(mp(a[mnab.se].fi + a[mnab.se].se, mnab.se));
			hx2.push(mp(a[mnab.se].fi, mnab.se)), hx3.push(mp(a[mnab.se].se, mnab.se));
		} else if( dc > da && dc > db && dc > 0 ) {
			ans += dc;
			tag[mna.se] = 1, hx2.push(mp(a[mna.se].fi, mna.se));
			tag[mnb.se] = 2, hx3.push(mp(a[mnb.se].se, mxb.se));
			tag[mxab.se] = 3;
			hn1.push(mp(-(a[mxab.se].fi + a[mxab.se].se), mxab.se));
			hn2.push(mp(-a[mxab.se].fi, mxab.se)), hn3.push(mp(-a[mxab.se].se, mxab.se));
		} else if( da > db && da > 0 ) {
			ans += da;
			tag[mna.se] = 1, hx2.push(mp(a[mna.se].fi, mna.se));
			tag[mxa.se] = 2, hx3.push(mp(a[mxa.se].se, mxa.se));
		} else if( db > 0 ) {
			ans += db;
			tag[mxb.se] = 1, hx2.push(mp(a[mxb.se].fi, mxb.se));
			tag[mnb.se] = 2, hx3.push(mp(a[mnb.se].se, mnb.se));
		} else break;
	}
	printf("%lld\n", ans);
}

int main() {
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);
	
	int T; scanf("%d", &T);
	while( T-- ) solve();
}

@details@

去年写了一个丢人的假贪心,还没发现连大样例都过不了 = =。

关于模拟费用流,除了直接优化连续最短路算法过程外,还有就是 WC2019 上讲的一类老鼠进洞问题(每次加边,动态维护费用流)。

这种用费用流的思想分析贪心问题(或者用贪心优化费用流过程)的好处是,它原本构造性的结论可以与费用流中众所周知的性质转化,更方便思考。

上一篇:【洛谷5470】[NOI2019] 序列(模拟费用流)


下一篇:NOI2019游记