【DP|多重背包可行性】POJ-1742 Coins

69 篇文章 0 订阅
13 篇文章 0 订阅
Coins
Time Limit: 3000MS Memory Limit: 30000K
   

Description

People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch. 
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins. 

Input

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

Sample Output

8
4

Source

LouTiancheng@POJ

————————————————————男人的分割线————————————————————

思路:男人八题@楼天成之一——Coins。卡常数,很难用01背包转化的思想优化出来。那么考虑“可行性问题”。

不关心价值,仅仅关心是否能填满某容量的背包,即题述“价格”。可以用数组标记法。

首先是某种价格的存在标记vis[ ]。然后是当前面额的使用张数user[ ],先枚举每种面额,然后对于每种面额,从该面额开始,直到价格上限,正向递推。状态的转移条件是:对每种面额,通过增加该面额的钞票产生新的价格。

For i : 1 ~ n

因为新的面额还未使用过,因此先清空对于所有价格的使用张数,这其实是滚动数组。

For j : 0 ~ V

    user = 0

从当前面额开始推,即仅有一张当前钞票。(因为小于当前面额的价格之前推过了)从该价格开始枚举到价格上限,一旦出现了新的价格,(这必然是因为又增加了一张当前面额的钞票,想一想为什么)并且使用新的这张钞票之前的价格已经被推出来,则ans++。

For j : cost[i] ~ V
    If new_price & old_price & user_price + 1 <= bag[i]
        new->old; user+1; ans++;

首先在考虑当前面额之前,即 j - cost[i] 的价格必须要有,例如当前是2元面额的时候:2元价格,那么0元的价格要有。3元价格,那么1元的价格要有。这样才能通过增加一张2元面额来达到3元价格。

关键就在这里——通过增加一张当前面额来达到当前价格。因此old_price其实就是 j - cost[i]。

该思路的精华,在于利用两个辅助数组vis[ ]以及user[ ]来刻画状态,省下了递推过程中所有能省的常数。由于并不关心物品的价值,因此并不需要一个标准的dp[ ]值以及状态转移方程,仅仅需要用辅助数组来刻画状态,并且在状态能够转移的时候更新答案,答案仅仅跟状态有关。

代码如下:(源代码参见——贰圣

/****************************************/ 
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
 #include <algorithm>
 #include <cmath>
 #include <stack>
 #include <queue>
 #include <vector>
 #include <map>
 #include <string>
 #include <iostream>
 using namespace std;
/****************************************/
const int N = 105;
const int M = 100005;
int cost[N], bag[N], user[M];
bool vis[M];

int main()
{
	int n, V, ans;
	while (~scanf("%d%d", &n, &V)) {
		if(!n && !V)	break;
		for (int i = 1; i <= n; i++)
			scanf("%d", &cost[i]);
		for (int i = 1; i <= n; i++)
			scanf("%d", &bag[i]);
		for(int i = 1; i <= V; i++)
			vis[i] = false;
		vis[0] = true;
		ans = 0;
		for(int i = 1; i <= n; i++) {
			for(int j = 0; j <= V; j++)
				user[j] = 0;//对于各种价格新的面额一张都没有贡献
			for(int j = cost[i]; j <= V; j++) {//从cost[i]开始推,即增加一张新的面额,之后一元一元递增,构造新的价格
				if(!vis[j] && vis[j-cost[i]] && user[j-cost[i]] + 1 <= bag[i]) {//如果该价格没有出现过,而且能从增加新的一张该面额之前的价格推出来,而且当前面额使用张数没有超过上限,必然使用了新的一张钞票并且出现了新的解
					vis[j] = true;
					user[j] = user[j-cost[i]] + 1;//增加一张当前面额
					ans++;
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值