题意:判断最少删掉哪几个数,使得当前数组中所有的数无法分成两个不空的子数组,使得两个子数组之和相等。
做法:
先记数组总和为$S$。
-
$S$为奇数,首先最容易想到,如果初始时所有数的和是奇数,显然不用删掉任何数。
-
$S$为偶数
2.1、数组中存在奇数,将这个奇数删掉,显然最终结果也会转变成上一种情况
2.2、不存在奇数,考虑当前所有数能否构造出两个和相等的子数组,如果存在能否找到一个数,删掉这个数后无法构造出两个和相等的子数组。(这道题的重点)
- 利用01背包来解决这个问题,定义每个数字的价值和代价都是它本身
$f[i][j]$:表示用前$i$个数字,代价是$j$的最大价值。
所以判断当前所有数能否构造出两个和相等的子数组的依据就是f[n][s/2]
与S/2的大小关系
,之所以能这样判断是因为,当前每个数的价值和代价相等,因此价值不可能大于代价,所以对于$S/2$的代价,价值最大仅可能取到$S/2$,如果能取到$S/2$,就说明存在一种方案构造出两个和相等的子数组;反之,则不能。
如果f[n][s/2]==s/2
,我们就去数组中找且必然能找到一个$x$,(但是我不知道如何去证明必然能找到,有懂的大佬可以在下面评论一下,只是发现恰好这样写了能过)使得f[n][(S-x)/2]!=(S-x)/2
即可,同理,如果找到就说明将这个数删掉后,不存在一种方案构造出两个和相等的子数组。
所以这道题的关键就是能否利用01背包解决:能否找出若干个数表示出某个数。
#include <iostream>
#include <cstring>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
#include <cmath>
#include <unordered_map>
#define INF 0x3f3f3f3f
#define fi first
#define se second
#define met(a , b) memset(a , b , sizeof a);
#define pb push_back
using namespace std;
typedef long long LL ;
typedef pair<int , int> PII;
int f[110][200010];
void solve()
{
int n;
cin >> n;
vector<int> a(n + 1);
int m = n * 2000 , s = 0;
for(int i= 1; i <= n ; i++)
{
cin >> a[i];
s += a[i];
for(int j = a[i] ; j <= m ; j++)
f[i][j] = max(f[i - 1][j],f[i-1][j-a[i]]+a[i]);
}
if(s&1) puts("0");
else
{
if(f[n][s/2] != s/2)
{
puts("0");
return;
}
puts("1");
for(int i = 1 ; i <= n ; i++)
if(a[i] & 1 || (f[n][(s - a[i]) / 2] != (s - a[i]) / 2))
{
cout << i << endl;
return;
}
}
}
int main()
{
int T = 1;
//cin >> T;
for(int turn = 1 ; turn <= T ; turn++)
solve();
}