原题链接:https://ac.nowcoder.com/acm/contest/74362/E
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
小红拿到了一个数组,她准备选择若干元素乘以 -1,使得最终所有元素的和为 0。小红想知道最少需要选择多少个元素?
输入描述:
第一行输入一个正整数n,代表数组的大小。 第二行输入n个整数ai,代表数组的元素。 1≤n≤200 −200≤ai≤200
输出描述:
如果无法使得最终所有元素之和为 0,则输出 -1。 否则输出一个整数,代表选择元素的最小数量。
示例1
输入
3 1 -2 3
输出
1
说明
选择第一个元素即可。
示例2
输入
3 2 -2 3
输出
-1
解题思路:
这个题目要求我们选中数组中的一部分数使得乘以-1,然后最终数组和为0,要求的是最少要使得多少数乘以-1,才能使得最终数组的和为0,我之前遇到过一次和这个题目非常相似的题目,那个题目是Acwing5386. 进水出水问题,这俩个题目很相似,所以看到这个题目我很快就想到了解法,进水出水问题是将进水总量和出水总量的差值设置为状态,这里同样可以这样设置状态,我们将没有乘-1和乘了-1的部分的差值设为状态即可,由于这里的-200<=ai<=200,n=200,所以差值可能的范围为[-40000,40000],dp数组会出现负数下标,我们需要设置一个偏移量,我们可以设置偏移量为40000,那么差的范围变为[0,80000],然后dp处理即可。
dp处理过程:
状态定义:
操作指的是就某个位置乘以-1这个操作
定义f[i][j]表示处理完前i个物品并且操作部分和没有操作部分差为j的最少操作步数。
设B=40000为偏移值
初始化:
f[0][0]=0,由于要偏移B,所以f[0][B]=0
状态转移:
当前位置不操作,也就是不乘-1;
f[i][j]=f[i-1][j-a[i]]
当前位置操作,也就是乘以-1
f[i][j]=f[i-1][j+a[i]]+1
最终答案:
最终答案应该是操作部分和不操作部分差为0,所以答案是f[n][0],
由于还有偏移量,所以答案是f[n][B]
时间复杂度:dp状态个数为O(n*80000),转移为O(1),所以最终时间复杂度为O(80000*n)。
空间复杂度:dp数组为O(n*80000),所以空间复杂度为O(80000*n)。
cpp代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=210,M=80010,B=40000;
int n;
int a[N];
int f[N][M];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(f,0x3f,sizeof f);
f[0][B]=0; //初始化
for(int i=1;i<=n;i++)
for(int j=0;j<=80000;j++)
{
//当前不乘-1,操作次数不变
if(j-a[i]>=0)f[i][j]=min(f[i][j],f[i-1][j-a[i]]);
//当前位置乘-1,操作次数加1
if(j+a[i]<=80000)f[i][j]=min(f[i][j],f[i-1][j+a[i]]+1);
}
/*
乘-1的个数肯定不会超过数组大小,
所以当这里的f[n][B]大于n时说明无法使得最终所有元素和为0
*/
if(f[n][B]<=n)printf("%d\n",f[n][B]);
else printf("%d\n",-1);
return 0;
}