题目描述
难度分:1700
输入n(2≤n≤100)和长为n的数组a(1≤a[i]≤2000)。
你需要删除a中的一些数,使a无法分成两个元素和相等的子序列。
输出最少要删除多少个数,以及这些数的下标(从1开始)。
注:子序列不要求连续。
输入样例1
4
6 3 9 12
输出样例1
1
2
输入样例2
2
1 2
输出样例2
0
算法
分类讨论+01
背包
先可以分类讨论,令sum=Σni=1a[i]
- 如果sum是个奇数,那根本就不用删任何数,肯定不能将a划分成两个累加和相等的子序列。
- 如果sum是个偶数,但是无法凑出和等于sum2的子序列,那也不用删任何数。可以用
01
背包来判断,此时问题转化成了每个数可以取也可以不取,是否可以凑出一个和为sum2的子集,定义状态dp[i]为是否可以凑出i。如果凑得出sum2,就需要删数,此时sum为偶数,随便删掉一个奇数就行;如果没有奇数,注意到将所有a[i]反复除以2答案是不变的,因此只需要不断除以2直到出现一个奇数即可(不需要真正除以2,出现奇数的时候一定每个数都已经除以了minni=1lowbit(a[i]),因此lowbit值最小的那个数就是需要删除的数)。
复杂度分析
时间复杂度
算法流程的瓶颈在于01
背包,时间复杂度为O(nm),其中m=Σni=1a[i]2。这里值得一提的是,如果是sum为偶数,且凑不出和为sum2的子序列,同时所有a[i]都是偶数的情况下,找到lowbit最小值对应的索引时间复杂度为O(n);即使按照所有数不断除以2来实现,时间复杂度也只有O(nlog2A),本题中A≤2000,最多除以10次左右一定会出现奇数,所以这一部分不会成为算法的瓶颈。
空间复杂度
空间瓶颈也是01
背包的DP
数组dp,优化掉第一维后额外空间复杂度为O(m)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int n, a[N];
int lowbit(int x) {
return x&-x;
}
int main() {
scanf("%d", &n);
int tot = 0, index = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
tot += a[i];
if(a[i]&1) {
index = i;
}
}
if(tot&1) {
puts("0");
exit(0);
}
int target = tot >> 1;
bool dp[target + 1] = {0};
dp[0] = true;
for(int i = 1; i <= n; i++) {
for(int j = target; j >= a[i]; j--) {
dp[j] |= dp[j - a[i]];
}
}
if(dp[target]) {
if(index) {
printf("%d\n%d\n", 1, index);
}else {
int minlowbit = lowbit(a[1]);
index = 1;
for(int i = 2; i <= n; i++) {
if(lowbit(a[i]) < minlowbit) {
minlowbit = lowbit(a[i]);
index = i;
}
}
printf("%d\n%d\n", 1, index);
}
}else {
puts("0");
}
return 0;
}