hdu 2126 Buy the souvenirs(求方案数的背包)

今天上午给学弟讲背包的时候,看到背包里面有一段关于求方案数的背包的论述。那会儿我自己也不太明白所以就没讲,结果下午就做了一道这样的题目。

有n件物品,旅客一共有m块大洋。第一个问题,旅客最多可以买多少件物品?请注意,这里是多少件,不是价值最大。所以这个非常好求,将所有的物品按照价值排序,先买便宜的,再买贵的。贪心的思想。这个地方有些细节需要处理,如果所有物品的价值总和比旅客的钱少,那么就只有一个方案,旅客可以买走所有的物品。如果旅客的钱数连第一件物品都买不起,那么就直接输出”Sorry,you can't buy anything.“。

用这种方法,我们可以求出旅客最多买多少件物品,求出之后,物品的价格就有了两种属性,一种是钱数,一种是件数。也就是买一件物品需要的消耗是它的价格的钱数和1件物品的份额。在背包九讲中,这个叫做二维费用的背包问题。

如果是求最优方案,这个问题依旧毫无压力。但是现在的问题是求方案总数。

我们用dp[j][k]表示花费j元买k件物品的方案数,实际上很容易我们就可以得到dp[j][k]=dp[j][k]+dp[j-a[i]][k-1]。关于这个方程有两个需要特别解释的地方,第一个这个是空间优化后的方程,优化后的原理参见背包九讲第一讲01背包,这里不再叙述。我们需要知道的是dp[j][k]在这里指的是dp[i-1][j][k],也就是在考虑过第i-1件物品后dp[j][k]的方案数。这个解释了这个dp[j][k]为什么可以直接继承过来。第二个需要解释的是,在dp[j][k]和dp[j-a[i]][k-1]中有没有相同的方案,即我们有没有冒着重复计算的风险将两者相加。答案是没有。因为在dp[j-a[i]][k-1]这些方案中都有第i件物品,我们说过dp[j][k]实际上是dp[i-1][j][k],其中根本没有第i件物品的影子,所以两者不可能有重复的方案。

实际上如果了解第k大背包的算法的话,会对解决这道题有很大帮助。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 505
int dp[N][35];
int a[N];
int cmp(const void *a,const void *b)
{
	return *(int *)a-*(int *)b;
}
int Max(int x,int y)
{
	if(x>y)
		return x;
	else
		return y;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n,m;
		scanf("%d%d",&n,&m);
		int sum;
		sum=0;
		int flag;
		int i,j,k;
		for(i=1;i<=n;i++)
			scanf("%d",&a[i]);
		qsort(a+1,n,sizeof(a[0]),cmp);
		flag=0;
		for(i=1;i<=n;i++)
		{
			sum+=a[i];
			if(sum<=m)
				flag=i;
		}
		if(sum<=m)
		{
			printf("You have 1 selection(s) to buy with %d kind(s) of souvenirs.\n",n);
			continue;
		}
		else if(a[1]>m)
		{
			printf("Sorry, you can't buy anything.\n");
			continue;
		}
		memset(dp,0,sizeof(dp));
		for(i=0;i<=m;i++)
			dp[i][0]=1;
		for(i=1;i<=n;i++)
		{
			for(j=m;j>=a[i];j--)
			{
				for(k=flag;k>=1;k--)
					dp[j][k]=dp[j][k]+dp[j-a[i]][k-1];
			}
		}
		if(dp[m][flag]==0)
			printf("Sorry, you can't buy anything.\n");
		else
			printf("You have %d selection(s) to buy with %d kind(s) of souvenirs.\n",dp[m][flag],flag);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值