HDU 2844 Coins (POJ1742也是这道)
http://acm.hdu.edu.cn/showproblem.php?pid=2844
题意:
现在有价值val[1],val[2],…val[n]的n种硬币, 它们的数量分别为num[i]个. 然后给你一个m, 问你区间[1,m]内的所有数目, 由之前n种硬币来构造(即选取某些硬币使得这些硬币的价值和等于[1,m]区间的特定数), 最多能构造出这m个数中的多少个?
分析:
①:显然是背包装满问题(本题只不过是cost和value值一样了),相比于常规背包只是初始化不同,传递所装物品的最大值以外,还要传递背包知否装满,初始化为-INF(装不满),dp[0]初始化为0(装满)。这样在传递的状态的时候,没装满的背包都是负值。
②:看到有人这样定义:dp[i][j]表示只用前i种硬币且硬币总价值总价值正好等于j时, 有多少种方法.
有点像前面的这道题 数的划分
作者:http://blog.csdn.net/u013480600/article/details/40553757
但是他这个有个问题,就是~为啥dp数组里会出现负值,就是把最后的if(dp)改成if(dp>0)会wrong;
不知道是不是初始化的原因:dp[i][0]=1;
③:《挑战程序设计竞赛》有种这样的做法:
dp[i][j]:用前i种硬币就能得到价值为j的时候,第i种硬币最多能剩余多少个(不能得到的时候,就剩下-1)
递推关系:
dp[i][j]=num[i] //dp[i-1][j]>=0,用前i-1种硬币就能达到价值i了,所以不需要第i种了
dp[i][j]=-1 //j<value[i]或者dp[i][j-value[i]]<=0。加一个第i种硬币就超过j了,或者加到j-value[i]这种硬币就没了。实际上这两个条件可以有下面的递推关系的出来(红色大于0,再根据等式~~~)
dp[i][j]=dp[i][j-value[i]]-1
这种方法好难想哦
初始化全为-1,最后遍历一遍数组,大于等于0的就符合。
④:回顾下原始的背包问题,背包问题是让求最优值,方法1就是完全套用的多重背包的模板,只不过修改了初始化,这里我感觉有两种浪费:
1.对一种物品,假设体积为3的背包都装不满了,体积为4.5.6.7...的当然也不会装满了,都没有必要往下走了。
2.对一种物品,能装满体积为3的背包了,就没必要后面再对体积为3的背包进行判断了。
这也是在poj上,为什么直接套用多重背包模板会超时的原因吧?
为了避免繁琐的减枝,不妨直接修改dp的定义。
bool dp[j]:硬币能否凑成价值j。
下面分别是套用多重背包(杭电可以AC),方法③,方法④(推荐)的AC代码
多重背包:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
# define INF 0x3f3f3f3f
using namespace std;
int a[105], c[105];//价值,数量。
int dp[100010];
int n, m;//钱的种类,最大价格
void Complete_Pack(int cost)
{
for (int i = cost; i <= m; i++)
{
dp[i] = max(dp[i], dp[i - cost] + cost);
}
}
void Zero_One_Pack(int cost)
{
for (int i = m; i >= cost; i--)
{
dp[i] = max(dp[i], dp[i - cost] + cost);
}
}
void Muilti_Pack(int cost, int num)
{
if (cost*num >= m)
{
Complete_Pack(cost); return;
}
int k = 1;
while (k < num)
{
Zero_One_Pack(cost*k);
num -= k;
k *= 2;
}
Zero_One_Pack(cost*num);
}
int main()
{
while (cin>>n>>m,n||m)
{
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]); //价值
for (int i = 1; i <= n; i++)
scanf("%d", &c[i]); //数量
fill(dp+1, dp + m+1, -INF);
dp[0] = 0;
for (int i = 1; i <= n; i++)
Muilti_Pack(a[i], c[i]);
int ans = 0;
for (int i = 1; i <= m; i++)
{
if (dp[i]>0)
ans++;
}
printf("%d\n", ans);
}
return 0;
}
方法③:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n, m;
int a[105], c[105];
int dp[100005];
int main()
{
while (cin >> n >> m, n && m)
{
for (int i = 1; i <= n; i++)cin >> a[i];//价值
for (int i = 1; i <= n; i++)cin >> c[i];//数量
memset(dp, -1, sizeof(dp));
dp[0] = 0; //并非真正的初始化
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;
}
int num=0;
for (int j = 1; j <= m; j++)
{
if (dp[j] >= 0)num++;
}
cout << num << endl;
}
system("pause");
}
方法④:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int a[105], c[105];//价值,数量。
bool dp[100005];
int use[100005];
int main()
{
int n, m, ans;
while (~scanf("%d%d",&n,&m), n || m)
{
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &c[i]);
fill(dp + 1, dp + 1 + m, false);
dp[0] = true;
ans = 0;
for (int i = 1; i <= n; i++)
{
fill(use, use + m + 1, 0); //use[j]表示:凑成价值为j的时候,硬币i用了多少了。
for (int j = a[i]; j <= m; j++)
{
if (!dp[j] && dp[j - a[i]] && use[j - a[i]] + 1 <= c[i]) //价值j还没凑够,j-a[i]凑够了,还有硬币(第i种)可以用。
{ //三个条件卡住,第二个循环好多没了
dp[j] = true;
use[j] = use[j - a[i]] + 1;
ans++;
}
}
}
printf("%d\n", ans);
}
}