跳转至

CF618F Double Knapsack

题目描述

给你两个可重集A,B,AB的元素个数都为nn\le1000000,它们中每个元素的大小x\in[1,n]。 请你分别找出A,B的可重子集,使得它们中的元素之和相等。

输入格式

第一行为一个整数n,表示两个子集的大小。

第二、三行皆为n个整数,分别表示AB的元素。

输出格式

如果无解,请输出-1。如果有解,第一行输出A的可重子集中元素的个数,第二行输出该子集中元素在A 中对应的下标;第三行输出B的可重子集中元素的个数,第四行输出该子集中元素在B中对应的下标。

数据可能存在多组解,输出一组即可。


思路

不得不说此题十分神仙。
先讲做法。
读入数据保存在数组a,b中。
sa_i=\sum_{i=1}^n a_i,sb_i=\sum_{i=1}^n b_i假设sa[n]\ge sb[b] 如果不是的话交换就OK了XD
对于每个sa_i,(i\in[0,n]),选取sb_j使sb_j小于等于sa_ij最大。
如果sa_i-sb_j的值之前未出现过,将sa_i-sb_j记录,并保存此时的i,j
如果该值之前出现过,直接将a,b中之前出现位置到a,b目前的位置i,j分别输出即可。
由于保证所有数都在[1,n]之间,因此若sb_jsb中最大的小于sa_i的数,sa_i-sb_j小于n,有n种取值。
0nn+1种取值,根据抽屉原理,肯定能遇到sa_i-sb_j之前出现过的情况,因此肯定有解。

代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;
#define i64 long long
#define MAXN 1000005

int N, flg, p1, p2, t;
i64 a[MAXN], b[MAXN];
int ra[MAXN], rb[MAXN];

inline void print( int x, int y ){
    printf( "%d\n", y - x + 1 );
    for ( int i = x; i <= y; ++i ) printf( "%d ", i );
    putchar('\n'); 
}

int main(){
    scanf( "%d", &N ), b[N + 1] = 0x7f7f7f7f7f7f7f7f;
    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] ) swap( a, b ), flg = 1;
    memset( ra, -1, sizeof ra );
    for ( p1 = p2 = 0; p1 <= N; ++p1 ){
        while( b[p2 + 1] <= a[p1] ) ++p2;
        t = a[p1] - b[p2];
        if ( ra[t] != -1 ){
            flg ? print(rb[t] + 1, p2), print(ra[t] + 1, p1) : (print(ra[t] + 1, p1), print(rb[t] + 1, p2)); break;
        } ra[t] = p1, rb[t] = p2;
    } return 0;
} 

评论