[NOI2019]序列(模拟费用流)

题意:

有两个长度为n的序列,要求从每个序列中选k个,并且满足至少有l个位置都被选,问总和最大是多少。
\(1\leq l\leq k\leq n\leq 2*10^5\)
首先,记录当前考虑到的位置i,第一个选的数量a, 第二个选的数量b,都被选的数量c,可以做到\(O(n^4)\)
卡常后能过\(n\leq 150\),有40分。
考虑正解:首先,看到这个范围,可以认为正解一定是贪心。
先看下\(n\leq 2000\),这个是网络流的范围。我们可以先建出费用流,然后再变为模拟费用流,即贪心。
从源点向第一个序列连边,第二个序列向汇点连边。
然后,我们发现至少有l个位置都被选不太容易表示,因为左面向对应的右面连的边是分开的,无法放到一起考虑下限。
换一种思路:至少有l个位置都被选,就是剩的位置不超过\(k-l\)
所以,我们可以这样建图:
1、从源点向第一个序列连边,第二个序列向汇点连边。
2、对应位置连流量为1的边。
3、第一个序列都向点a连边,点b向第二个序列连边。a到b连容量\(k-l\)的边。
求出流量为k的最大费用流即可。这样据说有64分。
考虑模拟费用流优化:
首先,记录s表示a到b的剩余流量。
1、若\(s>0\),则可以通过a到b的边增广,即从两个序列中各选一个最大数,并把s减去1。

2、可以直接走左右的对应连边增广,即选一个左右相加最大的位置。
3、可以走\(S->x->x'->b->y'->T\),走\(x'->b\)的前提是x在第二个序列中被选。
即在第一个序列中选一个第二个序列中选的位置,再选一个第二个序列中被选的作为y。
4、与3对称,即在第二个序列中选一个第一个序列中选的位置,再选一个第一个序列中被选的。
因为是最长路增广,所以在2,3,4中选最大的。以上操作可以用5个优先队列完成。

然而,我们发现过不去样例。
其实,算法是对的,但我们少了一些情况(特判):
首先,还可以走\(S->x->x'->b->a->y->y'->T\)。(情况5)
就是在第二个序列中选一个第一个序列中选的位置,再在第一个序列中选一个第二个序列中选的位置,
因为退了a到b的流,要把s加1。

在1中,若选的两个位置相同,则不用减s。若选的位置在另一序列中已被选,则为情况3或4,不用减s。
若选的位置在另一序列中已被选,则为情况5,把s加1。
在3,4中,可能会转移到情况5,此时要把s加1。

加上这些后就能过了,实现时要注意细节。

代码:

#include <stdio.h> 
#include <queue> 
using namespace std;
struct SJd {
	int z,	i;
	SJd() {}
	SJd(int Z, int I) {
		z = Z;i = I;
	}
};
bool operator < (const SJd a, const SJd b) {
	return a.z < b.z;
}
#define prio priority_queue < SJd > 
#define ll long long 
int sa[200010],sb[200010],ba[200010],bb[200010];
prio pa,pb,pc,pd,pe,em;
inline int read() {
	char ch;
	while ((ch = getchar()) < '0' || ch > '9');
	int rt = ch - '0';
	while ((ch = getchar()) >= '0' && ch <= '9') rt = (rt << 3) + (rt << 1) + ch - '0';
	return rt;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		pa = pb = pc = pd = pe = em;
		int n,k,l;
		n = read();
		k = read();
		l = read();
		for (int i = 0; i < n; i++) sa[i] = read();
		for (int i = 0; i < n; i++) sb[i] = read();
		for (int i = 0; i < n; i++) {
			pa.push(SJd(sa[i], i));
			pb.push(SJd(sb[i], i));
			pe.push(SJd(sa[i] + sb[i], i));
			ba[i] = bb[i] = false;
		}
		int sy = k - l;
		ll ans = 0;
		for (int i = 0; i < k; i++) {
			SJd ra,rb,rc,rd,re;
			while (!pa.empty()) {
				ra = pa.top();
				if (!ba[ra.i]) break;
				pa.pop();
			}
			while (!pb.empty()) {
				rb = pb.top();
				if (!bb[rb.i]) break;
				pb.pop();
			}
			if (sy > 0) {
				if (bb[ra.i]) sy += 1;
				if (ba[rb.i]) sy += 1;
				if (ra.i == rb.i) sy += 1;
				ba[ra.i] = bb[rb.i] = true;
				pc.push(SJd(sa[rb.i], rb.i));
				pd.push(SJd(sb[ra.i], ra.i));
				sy -= 1;
				ans += ra.z + rb.z;
				continue;
			}
			while (!pc.empty()) {
				rc = pc.top();
				if (!ba[rc.i]) break;
				pc.pop();
			}
			while (!pd.empty()) {
				rd = pd.top();
				if (!bb[rd.i]) break;
				pd.pop();
			}
			while (!pe.empty()) {
				re = pe.top();
				if (!ba[re.i] && !bb[re.i]) break;
				pe.pop();
			}
			int ma = -1,
			lx = -1;
			if (!pc.empty() && !pb.empty() && rc.z + rb.z > ma) ma = rc.z + rb.z,lx = 1;
			if (!pd.empty() && !pa.empty() && rd.z + ra.z > ma) ma = rd.z + ra.z,lx = 2;
			if (!pe.empty() && re.z > ma) ma = re.z,lx = 3;
			ans += ma;
			if (lx == 1) {
				if (ba[rb.i]) sy += 1;
				ba[rc.i] = bb[rb.i] = true;
				pc.pop();
				pb.pop();
				pc.push(SJd(sa[rb.i], rb.i));
			} else if (lx == 2) {
				if (bb[ra.i]) sy += 1;
				ba[ra.i] = bb[rd.i] = true;
				pa.pop();
				pd.pop();
				pd.push(SJd(sb[ra.i], ra.i));
			} else if (lx == 3) {
				ba[re.i] = bb[re.i] = true;
				pe.pop();
			}
		}
		printf("%lld\n", ans);
	}
	return 0;
}
posted @ 2019-08-25 21:54  lnzwz  阅读(370)  评论(0编辑  收藏  举报