CF618F Double Knapsack 题解
题意:
给你两个可重集 $A, B$,$A, B$ 的元素个数都为 $n$,它们中每个元素的大小 $x\in [1,n]$。请你分别找出 $A, B$ 的子集,使得它们中的元素之和相等。
$n\leq 10^6$。
什么神仙构造题 姑且认为是打表找规律的构造题吧
结论:一定有解,并且存在连续子序列(子段)的解。
这里的连续子序列甚至不用排序,就是 $A,B$ 任意排列中的某个连续子序列
证明:(鸽巢原理)
设 $A_i = \sum_{j=1}^{i}a_j,B_i=\sum_{j=1}^{i}b_j$ ,并定义 $A_0 = B_0=0$
不失一般性1,我们假设 $A_n \le B_n$ 。
定义 $f : \mathbb{N} \to \mathbb{N}$ , $f(i)~(0\le i \le n)$ 表示满足 $A_i \ge B_{f(i)}$ 的最大整数,显然 $0\le f(i) \le n$ 。
由于 $A_i < B_{f(i)+1}$ ,则
移项得
因为 $1\le b_{f(i)+1} \le n$ ,所以
则 $A_i - B_{f(i)}$ 有 $n$ 种取值,而 $0\le i \le n$ ,即 $i$ 有 $n+1$ 种取值
根据鸽巢原理,可知至少有两个 $A_i - B_{f(i)}$ 是相等的。
这意味着,存在 $l_1<r_1$ 满足
记 $l_2=f(l_1),r_2=f(r_1)$ ,移项可得
故结论成立。
那么怎么求出 $f(i)$ 呢,显然这个 $f(i)$ 是单调不降的,于是直接维护一个指针就好了
然后我们用个桶,边跑边记录每个 $A_i - B_{f(i)}$ 对应的 $i$ 和 $f(i)$
直到碰到一个 $A_i -B_{f(i)}$ ,之前出现过了,就输出答案。
注意要单独开个数组判断 $A_i - B_{f(i)}$ ,因为 $i$ 可以为 $0$ 。
时间复杂度 $O(n)$
代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdarg>
#include <cmath>
#include <iomanip>
#include <random>
#include <bitset>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N ((int)(1e6+15))
bitset<N> vis;
int n,l1,l2,r1,r2,flag,a[N],b[N],l[N][2];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i], a[i] += a[i-1];
for(int i=1; i<=n; i++) cin >> b[i], b[i] += b[i-1];
if(a[n] > b[n]) {flag = 1; for(int i=1; i<=n; i++) swap(a[i], b[i]);}
for(int i=0,j=0; i<=n; i++)
{
while(a[i] >= b[j] && j<=n) ++j; --j;
if(vis[a[i]-b[j]])
{
l1 = l[a[i]-b[j]][0] + 1;
l2 = l[a[i]-b[j]][1] + 1;
r1=i; r2=j; break;
}
l[a[i]-b[j]][0] = i; l[a[i]-b[j]][1] = j;
vis[a[i]-b[j]] = 1;
}
if(flag) {swap(l1,l2); swap(r1,r2);}
cout << r1 - l1 + 1 << '\n';
for(int i=l1; i<=r1; i++) cout << i << " \n"[i==r1];
cout << r2 - l2 + 1 << '\n';
for(int i=l2; i<=r2; i++) cout << i << " \n"[i==r2];
return 0;
}
注释:
1:不失一般性(without loss of generality,缩写:WLOG、WOLOG或w.l.o.g.)是数学中一个常见的表达。 其被用在证明中将前提条件明确到个例上时,说明该个例能代表普遍情况,而非一种特例。
参考文献:
[1] https://www.luogu.com.cn/blog/Sham-Devour/solution-cf618f