【CodeForces 618F】Double Knapsack
链接:
题目大意:
给你两个可重集 \(a,b\),\(a,b\) 的元素个数都为 \(n\),它们中每个元素的大小 \(x\in [1,n]\)。请你分别找出 \(a, b\) 的子集,使得它们中的元素之和相等。
正文:
这是一道非常牛逼的构造题。
设 \(A,B\) 分别为 \(a,b\) 的前缀和。猜想有解且一定连续。
若 \(A_n\leq B_n\),对于每个 \(i\in[1,n]\),都能找到对应的 \(j\leq n\) 使得 \(B_j\leq A_i,B_{j+1}>A_i\)。也就是 \(A_i<B_j+b_{j+1}\),得到 \(0\leq A_i-B_j<n\),所以一共有 \(n+1\) 个 \(A_i-B_j\)。但又因为只有 \(n\) 个 \(i\),鸽笼原理,必有 \(A_{i_1}-B_{j_1}=A_{i_2}-B_{j_2}\)。\(\mathcal{O}(n)\) 桶排即可。
代码:
const int N = 2e6 + 10;
inline ll Read()
{
ll x = 0, f = 1;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') f = -f, c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x * f;
}
int n;
ll a[N], b[N];
ll bucket[N][2];
bool flag;
void Sol1(int i, int j)
{
printf ("%d\n", i - bucket[a[i] - b[j]][0]);
for (int k = bucket[a[i] - b[j]][0] + 1; k <= i; k++)
printf ("%d ", k);
}
void Sol2(int i, int j)
{
printf ("%d\n", j - bucket[a[i] - b[j]][1]);
for (int k = bucket[a[i] - b[j]][1] + 1; k <= j; k++)
printf ("%d ", k);
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
memset (bucket, -1, sizeof bucket);
n = Read();
for (int i = 1; i <= n; i++) a[i] = Read() + a[i - 1];
for (int i = 1; i <= n; i++) b[i] = Read() + b[i - 1];
if (a[n] < b[n]) swap (a, b), flag = 1;
for (int i = 0, j = 0; i <= n; i++)
{
for (; b[j] <= a[i] && j <= n; j++);
j--;
if (~bucket[a[i] - b[j]][0])
{
if (flag) {Sol2(i, j); putchar(10); Sol1(i, j);}
else {Sol1(i, j); putchar(10); Sol2(i, j);}
break;
}
else bucket[a[i] - b[j]][0] = i, bucket[a[i] - b[j]][1] = j;
}
return 0;
}