51nod 1007正整数分组(01背包变形)

  题目大意就是给n个数,分2组,求2组和之差最小。

  这题我一开始写的dfs结果t了,后面看了题解说是0背包变形才恍然大悟。第一种解法:先把n个数的和sum求出来,要使得2组数和之差尽可能小,必定是要使得一组逼近sum/2,于是从这里入手,我们就转而求从n个数选若干个数,在和<=sum/2的情况下和能取得的最大值,这个时候就很明显了,这里把一个数的大小同时作为该数字的价值和体积,dp[i][j]表示从前i个数选若干个数放进容积为j的背包中能得到的最大价值,然后可以滚掉一维,最后答案就是dp[sum/2],表示从前n个数选若干个数,和不超过sum/2的情况下的最大和.

看了其他大佬的博客还发现了第二种解法,定义f[i][j]为把前i个数分2组,差为j是否可行,状态转移有3种,详细见代码注释。这里就不能滚掉一维了,因为 第一种转移f[i][j]能从f[i-1][a[i]+j]转移过来。

第一种解法

#include<bits/stdc++.h>
using namespace std;
int dp[5005],a[105];
int main()
{
    int n,sum=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&(a[i])),sum+=a[i];
    for(int i=1;i<=n;i++)
        for(int j=sum/2;j>=a[i];j--)
            dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
    cout<<sum-dp[sum/2]-dp[sum/2]<<endl;
    return 0;

}

第二种解法

#include<bits/stdc++.h>
using namespace std;
bool f[105][10000];
int a[105];
int main()
{
    int n,sum=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&(a[i])),sum+=a[i];
    f[0][0]=1;
    for(int j=1;j<=sum;j++) f[0][j]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=sum;j++)
        {
            f[i][j]|=f[i-1][a[i]+j];//转移1,看成是把a[i]放入了和较小的那组,完了之后和较小的那组依然是和较小的那组
            if(j-a[i]>=0)
                f[i][j]|=f[i-1][j-a[i]];//转移2,看成是把a[i]放入和较大的那组了,完了之和2组的差更大了
            if(a[i]-j>=0)
                f[i][j]|=f[i-1][a[i]-j];//转移3,看成是把a[i]放入了和较小的那组,完了之后和较小的那组变成了和较大的那组
        }
    for(int j=0;j<=sum;j++)
        if(f[n][j])
        {
            cout<<j<<endl;
            return 0;
        }
}
原文地址:https://www.cnblogs.com/eason9906/p/11754818.html