(不易)POJ-1742 多重部分和,多重背包可行性

题目大意:传说中的男人八题,是男人就A这八题。有n种面额的硬币,面额个数分别为A_i、C_i,求最多能搭配出几种不超过m(1-m)的金额?


题目链接:点击打开链接


分析:

首先来看看朴素的方法:

bool dp[i][j] := 用前i种硬币能否凑成j

递推关系式:

dp[i][j] = (存在k使得dp[i – 1][j – k * A[i]]为真,0 <= k <= C_i且下标合法)

然后三重循环ijk递推

这样的话复杂度为O(m*ΣC_i),肯定过不去。。


然后我们想到将问题转化为01背包,并利用二进制(具体可以看背包九讲)来优化

复杂度为O(m*ΣlogC_i),仍然TLE。。


我在钻研完全背包问题与多重背包问题(不仅是背包)时,曾不断想要将第①种方法中的k那一层循环利用与完全背包一样的方法除去,最终通过不断努力,终于找到了完全问题与多重问题之间在优化递推式时质的区别!!!发现通过自己这种方法也可以在O(nm)的复杂度里得出最终结果,只是苦于这道题的时间实在是卡得太紧,用标准方法(下面要讲的方法④)也花了2000ms,由于自己这种方法可能多了1一个常数倍的时间,所以最终还是TLE了。虽然很可惜没有自己A掉,但却对完全与多重背包有了一个更加深层次的理解,等刷完手上的几个背包问题,我将会写一篇文章来专门为大家阐述这种方法。


对于朴素的方法,这个算法每次只记录一个bool值,损失了不少信息。在这个问题中,不光能够求出是否能得到某个金额,同时还能把得出了此金额时A_i还剩下多少个算出来,这样直接省掉了k那重循环。

我们优化dp的状态:

状态:dp[i][j] : = 用前i种硬币凑成j时第i种硬币最多能剩余多少个( - 1表示配不出来)

转移:

若dp[i-1][j]>=0,即前i-1种可以配成j,所以根本用不到第i种,所以剩余C_i种  dp[i][j]=C_i

若j<a[i] || dp[i][j-a[i]]<=0,由于dp[i-1][j]<0,所以要想配成j起码得要有第i种,若j<a[i]则第i种用不到,所以前i种仍配不到j,若dp[i][j-a[i]]<=0,则说明配成j-a[i]时第i种已经无剩余或者甚至无法配成j-a[i],更别说配成j了,        dp[i][j]=-1

其他情况,由于a[i]还有剩,所以dp[i][j]相当于在dp[i][j-a[i]]的基础上多使用了一个a[i],此时   dp[i][j]=dp[i][j-a[i]]-1


最终找出所有>=0的dp[n][i]个数就行了(1<=i<=m)


附上朴素版,我自己的版本与最终AC版:

#include <iostream>     //朴素版
#include <algorithm>
using namespace std;
bool dp[100 + 16][100000 + 16]; // dp[i][j] := 用前i种硬币能否凑成j
int A[100 + 16];
int C[100 + 16];
int main(int argc, char *argv[])
{
	int n, m;
	while (cin >> n >> m && n > 0)
	{
		memset(dp, 0, sizeof(dp));
		for (int i = 0; i < n; ++i)
			cin >> A[i];
		for (int i = 0; i < n; ++i)
			cin >> C[i];
		dp[0][0] = true;
		for (int i = 0; i < n; ++i)
			for (int j = 0; j <= m; ++j)
				for (int k = 0; k <= C[i] && k * A[i] <= j; ++k)
					dp[i + 1][j] |= dp[i][j - k * A[i]];
		int answer = count(dp[n] + 1, dp[n] + 1 + m, true); // 总额0不算在答案内
		cout << answer << endl;
	}
	return 0;
}
 

#include<iostream>        //我自己的版本
using namespace std;
int a[105], c[105];
int n, m;
long long dp[2][100005];     //dp[i][j]表示前i个可以以多少种方式组成j
int main()
{
	while (scanf("%d%d", &n, &m) && n && m)
	{
		int ans = 0;
		memset(dp, 0, sizeof dp);
		dp[0][0] = 1;
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);
		for (int i = 1; i <= n; i++)
			for (int j = 0; j <= m; j++)
				dp[i % 2][j] = dp[(i - 1) % 2][j] + (j >= a[i] ? dp[i % 2][j - a[i]] : 0) - (j >= (c[i] + 1) * a[i] ? dp[(i - 1) % 2][j - (c[i] + 1)*a[i]] : 0);
		for (int i = 1; i <= m; i++)
			ans += dp[n % 2][i] > 0;
		printf("%d\n", ans);
	}
	return 0;
}

#include<iostream>        //最终AC版
#include<algorithm>
using namespace std;
int a[105], c[105];
int n, m;
int dp[100005];      //dp[i][j]表示用前i种硬币凑成j时第i种硬币最多能剩余多少个( - 1表示配不出来) 
int main()
{
	while (scanf("%d%d", &n, &m) && n && m)
	{
		int ans = 0;
		memset(dp, -1, sizeof dp);
		dp[0] = 0;
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		for (int i = 1; i <= n; i++)
			scanf("%d", &c[i]);
		for (int i = 1; i <= n; i++)
			for (int j = 0; j <= m; j++)
			{
				if (dp[j] >= 0) dp[j] = c[i];
				else if (j < a[i] || dp[j - a[i]] < 0) dp[j] = -1;
				else dp[j] = dp[j - a[i]] - 1;
				if (i == n&&j > 0) ans += dp[j] >= 0;
			}
		printf("%d\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值