将一堆正整数分为2组,要求2组的和相差最小。
例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的。
输入
第1行:一个数N,N为正整数的数量。 第2 - N+1行,N个正整数。 (N <= 100, 所有正整数的和 <= 10000
输出
输出这个最小差值
输入示例
5 1 2 3 4 5
输出示例
1
考虑最后分组情况,如果所有数的和为sum, 较小和的那组数一定不超过 [sum / 2]。我们的目标是使得和较小组的总和尽可能大。
我们的目标是从这n个数中选出一些数,这些数的总和不超过[sum / 2]且总和尽可能大。
那我们重新定义int f(i,j)表示从前i个数中选出的数,总和不超过j的时候能得到的最大的和。
则如果不选择ai f(i-1,j) = f(i,j)
如果选择ai,则f(i,j) = f(i, j - ai) j >= ai
第二维大小是[sum / 2]
初值是f(0, x) = 0 注意含义是总和“不超过”x的时候的最大值。
那么最终答案是什么? 根据定义,应该是f(n, [sum / 2])。
时间复杂度和空间复杂度依然是O(n * [sum / 2]) ,同样可以优化掉一维的空间复杂度。
综上所述,两种方法的时间复杂度都是O(n * sum)级别的,通常sum不是很大,比如说本题sum不超过10000,所以从实际角度上讲,这个复杂度比O(2n)强多了。
再顺带说一下如果要求出一组最优解的具体分配方案,怎么做?
别忘了,动态规划问题,求具体方案的惯用方法就是记录当前f(i,j)是由之前哪个状态得到的,然后一步一步小心谨慎地回退到起点,就像我们在搜索问题中记录之前地节点恢复出路径一样。
递推方程如下
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int a[105],dp[10005];
int main()
{
int n,i,j;
scanf("%d",&n);
int sum = 0;
for (i=0;i<n;i++)
{
scanf("%d",&a[i]);
sum += a[i];
}
memset(dp,0,sizeof(dp));
for (i=0;i<n;i++)
for (j=sum/2;j>=a[i];j--)
dp[j] = max(dp[j],dp[j-a[i]]+a[i]);
int summ = sum - dp[sum/2];
int cha = abs(summ-dp[sum/2]);
printf("%d\n",cha);
return 0;
}