ZOJ_Tug of War DP

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1880

题意:给定n个数,要求分成两组,每组的个数最多相差1,求两组数的和相差最小的情况。

算法: 经典DP。

分析:用dp[i][j] 表示i个人的体重之和为j是否可能,显然转移应该从dp[i-1][j-?]来转移,但是

这里有一个问题,就是最后选进来的那个人如果设为k的话,因为没有规定i个人的范围,因此

这里的状态表示就没有“无后效性”了。 这里可以这么看,我们可以增加一个状态k,用dp[k][i][j]

表示前k个人中选取i个人,体重之和为j是否可能。则得到转移方程为:

dp[k][i][j] = dp[k-1][i][j] || dp[k-1][i-1][j-w[k]],即考虑第k个人取或者不取。

其实这个状态的得出也可以通过另外一个方面来得到,即:dp[i][j] 的求解的时候,k的for循环

放在最外层,这样就可以保证每个数只被考虑一次。


代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define min(a,b) (a>b?b:a)
#define max(a,b) (a>b?a:b)
int n,sum;
int w[101];
int dp[101][45001];


void DP()
{
	memset(dp,0,sizeof(dp));	
	dp[0][0] = 1;
	
	for(int k=1;k<=n;k++)
	{
		for(int i=k;i>=1;i--)
		{
			for(int j=0;j<=sum;j++)
			{	
				if(dp[i-1][j] == 1)
				{
					dp[i][j+w[k]]  = 1;
					dp[i][j] = 1 ;	
				}	
			}
		}
	}	
	
	int ave = n/2,res=0x7fffffff,min_,max_;
	for(int j=0;j<=sum;j++)
	{
		if(dp[ave][j]==1 && dp[n-ave][sum-j]==1)
		{
			if(res > abs(sum-2*j)){
				res = abs(sum-2*j) ;
				min_ = min(j,sum-j);
				max_ = max(j,sum-j);
			}	
		}	
	}
	printf("%d %d\n",min_,max_);
}

int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		sum = 0 ;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&w[i]);	
			sum += w[i] ;
		}
		DP();
	}	
	
	return 0;	
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值