CF618F Double Knapsack

发布时间 2023-06-14 15:04:19作者: dbxxx

CF618F Double Knapsack

我们从 \(A\)\(B\) 中两个集合依次选数,维护一个量 \(d\),表示 \(A\) 中所选数的和 \(- B\) 中所选数的和,初始为 \(0\)

\(d \le 0\) 时,我们从 \(A\) 中选数,当 \(d > 0\) 时,我们从 \(B\) 中选数,直至我们试图选的时候,发现集合已经空了为止。

考虑 \(d\) 的变化。\(d \le 0\) 时,它会加上一个 \([1, n]\) 的正整数,因此 \(d\) 会变大成一个不超过 \(n\) 的整数。

\(d > 0\) 时,它会减去一个 \([1, n]\) 的正整数,因此 \(d\) 会变小成一个不少于 \(-n + 1\) 的整数。

也即 \(d\) 的变化值域是 \((-n, n]\)

如果 \(d\) 在变化的过程中,某次的值和先前某一次的值相同,那么这个变化过程中两个集合分别新增的数就分别是答案集合。比如 \(\{3\}\)\(\{1\}\)\(2\)\(\{3, 4, 5\}\)\(\{1, 9\}\) 也差 \(2\),那么我们就找到了答案 \(\{4, 5\}\)\(\{9\}\)

现在我们试图证明 \(d\) 一定会有两次相同。

如果试图取 \(A\) 中的数时发现取完了,也即我们从 \(A\) 中取了 \(n\) 次数,并且现在 \(d \in (-n ,0]\)

取数的前提条件就是 \(d \le 0\),因此这 \(n\) 次取数,每次取数 之前 的那个时刻都是一个 \(d \in (-n ,0]\) 的时刻。而最后我们又有一次时刻 \(d \in (-n, 0]\),意味着我们已经有了 \(n + 1\)\(d \in (-n, 0]\) 的时刻。而 \((-n, 0]\) 中的整数仅有 \(n\) 个,根据抽屉原理,一定有两次 \(d\) 相同。

如果试图取 \(B\) 中的数发现取完了,同理可证(注意到 \((0, n]\) 也是 \(n\) 个整数)。

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2023-06-14 13:42:39 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2023-06-14 14:52:39
 */
#include <bits/stdc++.h>
inline int read() {
	int x = 0;
	bool f = true;
	char ch = getchar();
	for (; !isdigit(ch); ch = getchar())
		if (ch == '-')
			f = false;
	for (; isdigit(ch); ch = getchar())
		x = (x << 1) + (x << 3) + ch - '0';
	return f ? x : (~(x - 1));
}

const int N = (int)1e6 + 5;
int a[N], b[N];
typedef std :: pair <int, int> pii;
pii st[N << 1];

int main() {
	int n = read();
	for (int i = 1; i <= n; ++i)
		a[i] = read();
	for (int i = 1; i <= n; ++i)
		b[i] = read();
	st[n] = {1, 1};
	for (int d = n, i = 1, j = 1; ; ) {
		if (d <= n)
			d += a[i++];
		else
			d -= b[j++];
		
		if (st[d].first) {
			int p = st[d].first, q = st[d].second;
			auto f = [](int l, int r) {
				printf("%d\n", r - l);
				for (int i = l; i < r; ++i)
					printf("%d ", i);
				puts("");
			};
			f(p, i);
			f(q, j);
			return 0;
		} else
			st[d] = {i, j};
	}
	return 0;
}