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