正整数分组(51nod)(dp)

将一堆正整数分为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;
} 

 

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页