题目描述
金银岛上的人使用金币,每种金币面值分别是 A1; A2; A3; : : : ; An 元。一天 Tony 决定在
附近商店买一个非常好的表,他想在付钱的时候不要找零,但是他发现他的钱包里每种金
币的数量分别只有 C1; C2; C3; : : : ; Cn 个。不过,Tony 知道这块表的价格不会超过 M 元金币
(他不知道表的精确价格)。不知他的付钱方式能否实现。你的任务是帮助 Tony 算一下,在 1::M 元范围内(包括边界),他钱包中的金币可以精
确支付多少种价格。
输入格式
- 输入包括多组测试数据。每组测试数据的格式如下:
- 第一行包括 2 个整数 n; M。
- 第二行包括 2n 个整数 A1; A2; A3; : : : ; An 和 C1; C2; C3; : : : ; Cn。
- 测试数据的最后一行有 2 个 0,这一行无需处理。
输出格式
每组测试数据输出一行,为一个整数,即能精确支付的价格种数。
数据规模与约定
- 对于 30% 的数据,1<=N<=30; 1<=M<=1000
- 对于 60% 的数据,1<=N<=60
- 对于 100% 的数据,1<=n<=100; 1<=M<=10^5 1<=Ai<=105; 1<=Ci<=1000
题解
- 由于数据量过大(如果尝试枚举复杂度为n*m*Ci)肯定会超时,就需要进行压缩。
- 如果转化为单纯的0-1背包问题,时间复杂度任为(O)n*m*Ci,空间复杂度为m(∑Ci)。
- 如果采用滚动数组,空间复杂度可降为m,但时间上任然会爆炸。
- 因此需要进行二进制转换大小压缩。例:13=1+2+4+6;在前面的(1+2+4)当中就可以实现1——7的所有情况的枚举,并且可以最大化的减少重复的金额出现。
for (int j=temp;j<temp+(int)(log(a[i].C)/log(2));j++) {
aa[j]=a[i].A*(1<<(j-temp));
- 是进行对于重复多次相同金额的压缩。时间复杂度降为(O)m∑log2(Ci)。
直接上代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define INF 0x7f7f7f
using namespace std;
int m,n;
void f(){
freopen("coin.in","r",stdin);
freopen("coin.out","w",stdout);
}
struct node{
int A,C;
}a[101];
int dp[100001],aa[100001];
bool check(int a,int b){
return a>b;
}
int main(){
f();
while(cin>>n>>m&&(n!=0&&m!=0)){
memset(dp,-INF,sizeof(dp));//初始化无限小
memset(aa,0,sizeof(aa));
int sum=0,t=1,ans=0;
for (int i=1;i<=n;i++) {
cin>>a[i].A;
}
dp[0]=0;
for (int i=1;i<=n;i++) {
cin>>a[i].C;
sum+=a[i].C*a[i].A;
int temp=t,ss=0;
for (int j=temp;j<temp+(int)(log(a[i].C)/log(2));j++) {
aa[j]=a[i].A*(1<<(j-temp));//进行二进制左移
ss+=1<<(j-temp);
t++;
}
if(ss<a[i].C) aa[t++]=a[i].A*(a[i].C-ss);//最后多余为a[i].C-ss单独拿出放在最后
}
for (int i=1;i<t;i++){
for (int j=m;j>=aa[i];j--){
dp[j]=max(dp[j],dp[j-aa[i]]+1);//0-1背包问题
}
}
for (int i=1;i<=m;i++) if(dp[i]>0) ans++;//最后统计结果
cout<<ans<<'\n';
}
}