北大ACM——1742,Coins(多重背包)

多重背包问题。
详解见博客:
https://blog.csdn.net/Estia_/article/details/83590060

这道题时间卡得超严,在看大佬的代码之前疯狂TLE。
代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
bool dp[maxn];	//用bool快超多 
int a0[105];
int main()
{
	int n,m,c;
	while(~scanf("%d%d",&n,&m)&&n&&m)
	{
		for(int i=1;i<=m;i++)		//原本i,j,k等都是在一开始定义好的,但疯狂TLE,就把它们弄成临时定义,结果好像快了一点
			dp[i]=0;
		dp[0]=1; 
		for(int i=1;i<=n;i++)
			scanf("%d",&a0[i]);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&c);
			if(a0[i]*c>=m)	//转化为完全背包
				for(int j=a0[i];j<=m;j++)
					dp[j]=dp[j]|dp[j-a0[i]];
			else		//转化为01背包
			{
				for(int k=1;k<=c;k<<=1)
				{
					int val=a0[i]*k;
					for(int j=m;j>=val;j--)
						dp[j]=dp[j]|dp[j-val];
					c-=k;
					if(!c)
						break;
				}
				if(c)
				{
					int val=a0[i]*c;
					for(int j=m;j>=val;j--)
						dp[j]=dp[j]|dp[j-val];
				}
			}
		}
		int sum=0;
		for(int i=1;i<=m;i++)
			sum+=dp[i];
		printf("%d\n",sum);
	}
	return 0;
}

其实后来一想,原题的题意理解错了,题目是要求问1~m之间有多少可以构成多少种价值,而不是说构成价值1-m之间的方案数有多少种。(虽然最终结果都一样)也因此,dp数组可以用成bool类型。
事实上,这道题也属于另外一种类型题,多重背包可行性分析,对于这类问题,还有一种O(n*m)的算法。
强推:http://www.doc88.com/p-2961204836432.html

状态:dp[i,j]表示用前i中物品构成价值j时,第i种硬币最多剩下多少种。
如果能够构成价值j,则0<=do[i,j]<=c[i],否则dp[i,j]=-1.
对于这个算法,每一步都要进行初始化:
if(d[i-1,j]>=0) dp[i,j]=c[i]
else dp[i,j]=-1;
状态转移方程:
for(j=0,j<=m-a[i],;j++)
if(dp[i,j]>0)
dp[i,j+a[i]]=max(dp[i,j+a[i]],dp[i,j]-1);
这些会在代码中解释。

代码如下:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;	
int dp[maxn]; 	//这里要用成一维数组,否则会爆内存(POJ这道题卡得真的是严)
int a0[105];
int main()
{
	int n,m,c;
	while(~scanf("%d%d",&n,&m)&&n&&m)
	{
		for(int i=1;i<=m;i++)		//最初的初始化
			dp[i]=-1;
		dp[0]=0;
		for(int i=1;i<=n;i++)
			scanf("%d",&a0[i]);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&c);
			for(int j=0;j<=m;j++)		
				if(dp[j]>=0)	dp[j]=c;	//相当于dp[i-1,j]>=0,即用前i-1种硬币已经能够构成价值j了,那么也就用不着第i种硬币了,故dp[i,j]=c
				else dp[j]=-1;	//否则,先把那些用前i-1种硬币无法构成的价值,都先赋值为-1
			for(int j=0;j<=m-a0[i];j++)
				if(dp[j]>0)		//如果dp[i,j]>0,说明这种价值j的方案存在而且第i种硬币还有剩余
					dp[j+a0[i]]=max(dp[j+a0[i]],dp[j]-1);	//那么更新dp[i,j+a0[i]],注意,原本dp[i,j+a0[i]]是可能为-1的,这一步就将前面用前i-1种硬币无法构成的价值当中,再次挑出那些可以由于第i种硬币的参与,而能够构成的价值
		}
		int sum=0;
		for(int i=1;i<=m;i++)
			if(dp[i]>=0)	//价值为j的方案存在
				sum++;
		printf("%d\n",sum);
	}
	return 0;
}

个人觉得,这种算法一个核心就是用前面已经能够构成的价值方案,去推出更多的价值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值