注: n不一定是偶数,也可能是奇数
如题所述,这是一个经典的01背包问题,但是在实现过程中遇到了很多的麻烦,花了三个小时才搞完,特此记录下。主要是分享一下解题过程中出现的错误以及相应的解决办法,希望下回不会再犯吧。
首先,先来一个简化版问题,把约束(平均分割)去掉,题目变成 元素个数为n的正整数数组,如何能把这个数组分成两个子数组,并使两个子数组之和最接近,这里没有平均分割要求,那么就是一个简单的01背包问题,即n件物品凑不超过sum/2的值,
定义 dp[i][j] 为前i个数凑数字j 是否可能 ,其中j的范围从1 到 sum/2, 显然 d[i][0]=1; 原数组为v,为了之后的谈论方便这里 v 的元素从1开始,但程序中仍从0开始
状态转移方程: dp[i][j] = dp[i-1][j-v[i]] || dp[i-1][j] j>=v[i] 时
=dp[i-1][j] j<v[i] 时
程序大概是这样的,我没测试不知道正确与否
for(int i=1;i<=v.size();i++)
for(int j=1;j<=sum/2;j++)
{
if(j>=v[i-1])
dp[i][j] = dp[i-1][j-v[i-1]]||dp[i-1][j];
else dp[i][j]=dp[i-1][j];
}
现在回到原问题,先简化一点,设n是偶数。 那么,现在变成从n个里选 n/2个数,使他们之和接近 sum/2,实际上这就是多了一维的限定条件, 变成了二维的背包,定义 d[n][i][j] 为从 前 n 个数里选 i 个数使他们和为 j是否可能
状态转移方程: dp[n][i][j] = dp[n-1][i-1][j-v[n]] || dp[n-1][i-1][j] j>=v[n] 时
=dp[n-1][i-1][j] j<v[i] 时
写成程序也很好写,但是这里是o(n^3)的空间复杂度(由于第三维j与sum有关,所以实际上会高的多),可以利用一些技巧(有些地方称这种方法为滚动数组)来简化空间复杂度,关于这一部分可以去研究背包九讲。下面这个函数返回两个子数组的最小差值
int divide_array(const vector<int> &v)
{
const int len =v.size();
if(!len) return 0;
int sum=0; //假设sum不会溢出
for(int i=0;i<len;i++)
sum+=v[i];
int h_sum = sum>>1; //即 sum/2
int h_len = len>>1;
vector<vector<bool> > dp(h_len+1,vector<bool>(h_sum+1,0