题目描述:
一家银行计划安装一台用于提取现金的机器。
机器能够按要求的现金量发送适当的账单。
机器使用正好N种不同的面额钞票,例如D_k,k = 1,2,…,N,并且对于每种面额D_k,机器都有n_k张钞票。
例如,
N = 3,
n_1 = 10,D_1 = 100,
n_2 = 4,D_2 = 50,
n_3 = 5,D_3 = 10
表示机器有10张面额为100的钞票、4张面额为50的钞票、5张面额为10的钞票。
东东在写一个 ATM 的程序,可根据具体金额请求机器交付现金。
注意,这个程序计算程序得出的最大现金少于或等于可以根据设备的可用票据供应有效交付的现金。
思路:
考虑时间:
- 是一个多重背包问题,每个物品有n_k件。
- 如果转化成0-1背包,则记忆化搜索和递推时间一样
- 可以使用二进制拆分,把每个物品有n_k件,不简简单单的分成n_k组,而是分成logN_K组
考虑空间:
- 使用了二进制拆分后,就变成了普通0-1背包问题
- 递推可以使用滚动数组,但记忆化搜索不能只用一个数组F[V],必须有额外的开销记录哪些物品选过
- 所以我们用二进制拆分转化成普通0-1背包问题,再用递推实现程序
总结:
- 想使用滚动数组就不要用记忆化搜索
- 把N拆成X个数,使得这X个数的和是N,则二进制拆分能使数的个数最少
代码:
#include <cstdio> #include <iostream> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAXN=1e5+5; int f[MAXN]; vector<int> w; //拆分 void split(int x,int v) { for(int bit=1;bit<=x;bit<<=1) { w.push_back(bit*v); x=x-bit; } if(x>0) w.push_back(x*v); } int main() { int Cash,N; while( scanf("%d %d",&Cash,&N)==2 ) { memset(f,0,sizeof(f)); w.clear(); for(int i=1;i<=N;i++) { int c,v; scanf("%d %d",&c,&v); split(c,v); } N=w.size(); //原来一共有N种物品,现在有w.size()种 for(int i=0;i<N;i++) for(int j=Cash;j>=0;j--) { if(j-w[i]>=0) f[j]=max(f[j],f[j-w[i]]+w[i]); } cout<<f[Cash]<<endl; } return 0; }