牛客周赛 Round 31 E.小红的子集取反【dp+设置偏移量】

原题链接: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 
#include 
#include 
#include 

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;
}

你可能感兴趣的:(动态规划,算法,动态规划)