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;
}