题干:

邮票分你一半

时间限制:1000 ms  |  内存限制:65535 KB

难度:3

描述

     小珂最近收集了些邮票,他想把其中的一些给他的好朋友小明。每张邮票上都有分值,他们想把这些邮票分成两份,并且使这两份邮票的分值和相差最小(就是小珂得到的邮票分值和与小明的差值最小),现在每张邮票的分值已经知道了,他们已经分好了,你知道最后他们得到的邮票分值和相差多少吗?

输入

第一行只有一个整数m(m<=1000),表示测试数据组数。
接下来有一个整数n(n<=1000),表示邮票的张数。
然后有n个整数Vi(Vi<=100),表示第i张邮票的分值。

输出

输出差值,每组输出占一行。

样例输入

2
5
2 6 5 8 9
3
2 1 5

样例输出

         0

         2

解题报告:

      这题看起来很简单但是其实还是有坑的!下面提供n多中ac的代码。坑就在于 ,sum有可能是奇数!!

AC代码:(网络版,至今不知为什么这么做可以,普通0-1背包)

#include<bits/stdc++.h>

using namespace std;
int n;
int v[1000 + 5];
int dp[100000 + 5];
int main()
{
	int t;
	int sum = 0;
	cin>>t;
	while(t--) {
		sum = 0;
		cin>>n;
		for(int i = 1; i<= n; i++) {
			scanf("%d",&v[i]);sum += v[i];
		}
		int yuan=sum;
		sum=(sum+1)/2;//这里直接sum=sum/2  也可以ac!!!即 +1与否不影响
		if(n == 1) {
			cout << v[1] << endl; continue;
		}

		memset(dp,0,sizeof(dp));
		for(int i = 1; i<=n; i++) 
			for(int j = sum; j>=v[i]; j--) {
				if(abs(j-dp[j]) < abs(dp[j-v[i]]+v[i]-j)) dp[j]=dp[j];
		        else dp[j]=dp[j-v[i]]+v[i];
			}
		cout << abs(yuan-dp[sum]-dp[sum]) << endl;
	}
	return 0 ;
 } 

对于上面这种方法,我只理解到了下面的内容:

      因为背包是一类问题,比如一般的0-1背包,你取dp[j] = max(),是因为你想得到价值最大的,所以你这个状态代表的是,前i个物品在j容量下的最大价值是dp[j]这么大。 

      所以对于这个题来说你想得到的是最接近一半的分值,所以你dp[j]中存的应该是最接近当前价值的, 即: 如果更接近当前价值,那我就更新他。

AC代码2:(也是普通0-1背包)

#include<bits/stdc++.h>

using namespace std;
int n;
int v[1000 + 5];
int dp[100000 + 5];
int main()
{
	int t;
	int sum = 0;
	cin>>t;
	while(t--) {
		sum = 0;
		cin>>n;
		for(int i = 1; i<=n; i++) {
			scanf("%d",&v[i]);sum += v[i];
		}
        int half = sum>>1 ;//这里用half = sum>>1 + 1就错了!!但是half = (sum+1)>>1是对的。。。当然你最后输出的时候需要加个abs()才能ac!!!
		memset(dp,0,sizeof(dp));
		for(int i = 1; i<=n; i++) {
			for(int j = half; j>=v[i]; j--) {
				dp[j] = max(dp[j],dp[j-v[i]] + v[i]);
			}
		}
		cout << sum-2*dp[half]<<endl;
	}	
	return 0 ;
 } 

最开始就是这么写的但是我的输出相当于是cout << 2*(half - dp[half] )<<endl;样例也过了,我也觉着我这样理解是没有问题的,举个例子:总分值为10,我3他7,那我俩的差值不就是  2*((10/2)-3)这么大吗?如果  我4他6,,也可以成立,然后就这么交上去了,然后wa。看了别人的代码才发现,原来是奇偶数出现了问题,因为我试的样例是偶数(10嘛),所以肯定看样例肯定看不出来问题,于是我就改成了保留原sum,新开一个half,然后最后输出的时候用sum去减,而不是2*half去减。也就是把最后的输出改成了AC代码中那样的形式。

 

AC代码3:(装满型0-1背包)

 #include<bits/stdc++.h>

using namespace std;
int n;
int v[1000 + 5];
int dp[100000 + 5];
int main()
{
	int t;
	int sum = 0;
	cin>>t;
	while(t--) {
		sum = 0;
		cin>>n;
		for(int i = 1; i<=n; i++) {
			scanf("%d",&v[i]);sum += v[i];
		}
//		sum=(sum+1)/2;
		if(n == 1) {
			cout << v[1] << endl; continue;
		}
		memset(dp,-0x3f3f3f3f,sizeof(dp));dp[0] = 0;
		for(int i = 1; i<=n; i++) {
			for(int j = sum; j>=v[i]; j--) {
				dp[j] = max(dp[j],dp[j-v[i]] + v[i]);
			}
		}
		int minn = 0x3f3f3f3f;
		for(int i = sum; i>=0; i--) {
			if(dp[i] >=0) {
				//cout << 2*(sum-dp[i])<<endl;break;
				minn = min(  abs(sum-dp[i]-dp[i]),minn);
			}
		}
		cout << minn<<endl;
	}
	
	
	return 0 ;
 } 

这段代码,是我在写完AC代码2,然后WA了之后,重新改了一下,不再有sum/=2这样的操作,而是直接扫到sum,然后从后往前一个个的扫,维护一个minn,最后输出minn,这样也AC了但是时间是500ms左右,是AC代码1,AC代码2 的两倍左右