题解 CF618F 【Double Knapsack】

MY(一名蒟蒻)

2021-02-15 14:39:18

题解

原题传送门

第一道黑题祭!

拿到这一题有些人啪的一下就输出无解,很快啊!

但是这作为一道构造题,无脑骗分是很容易被偷袭的。

大胆猜(kou)想(hu):一定有解,且一定存在连续子序列的解。

接下来尝试证明。

A_i 表示 a_n 的前缀和,B_i 表示 b_n 的前缀和。

不失一般性,我们假设 A_n ≤ B_n 。对于 0 ≤ i ≤ n ,我们定义 j 是满足 A_i ≥ B_j 的最大整数,显然 j < n

由于 A_n ≤ B_n ,则 A_i ≤ B_j + b_{j+1} ,故 0 ≤ A_i - B_j < n

由鸽笼原理,一定存在 $i_1 \not= i_2$,满足 $A_{i_1}$ - $B_{j_1}$ = $A_{i_2}$ - $B_{j_2}$ 。 同样不失一般性假定 $i_1 > i_2$,那么 $A_{i_1}$ - $A_{i_2}$ = $B_{j_1}$ - $B_{j_2}$。则连续子序列 {$a_{i_2+1}$, · · · , $a_{i_1}$} 和 {$b_{j_2+1}$, · · · , $b_{j_1}$} 就是一个合法的方案。 两个数列中分别找到子序列起点和终点,对于子序列的值开个桶记录一下即可,值域从 $1$ 到 $\text{n}$ 。 时间复杂度 $\text{O(n)}$ 。 --- ## Code ```cpp #include <cstdio> #include <iostream> using namespace std; const int N=1e6+10; int n,l[N][2],la,lb,ra,rb; long long a[N],b[N]; bool modify,cnt[N]; void swap(int &x,int &y) {x^=y^=x^=y; return ;} int main() { // freopen("work.in","r",stdin); freopen("work.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) {scanf("%lld",&a[i]); a[i]+=a[i-1];} for(int i=1;i<=n;i++) {scanf("%lld",&b[i]); b[i]+=b[i-1];} if(a[n] > b[n]) {modify=true; for(int i=1;i<=n;i++) swap(a[i],b[i]);}//不失一般性 for(int i=0,j=0;i<=n;i++) { for(;a[i]>=b[j] && j<=n;j++); j--;//移动j的位置 if(cnt[a[i]-b[j]]) {la=l[a[i]-b[j]][0]+1; lb=l[a[i]-b[j]][1]+1; ra=i; rb=j; break;}//找到答案 l[a[i]-b[j]][0]=i; l[a[i]-b[j]][1]=j;//记录 cnt[a[i]-b[j]]=true;//这个值已经搞出来 } if(modify) {swap(la,lb); swap(ra,rb);} printf("%d\n",ra-la+1); for(int i=la;i<=ra;i++) printf("%d ",i); printf("\n%d\n",rb-lb+1); for(int i=lb;i<=rb;i++) printf("%d ",i); // fclose(stdin); fclose(stdout); return 0; } ``` 这里解释一下为什么需要 $\text{cnt}$ 数组而不能直接`l[a[i]-b[j]][0]`判断是否在桶中已经存在值。 例如这种数据。 ``` 10 9 9 9 9 9 9 9 9 9 9 10 10 10 10 10 10 10 10 10 10 ``` 数列从第一项开始,而此时桶中会存下 $\text{0}$ ,而这个 $\text{0}$ 是有效数据。 ## $\text{Thank you for your reading!}

您的点赞和评论是对作者最大的支持!