[bzoj1042] [HAOI2008]硬币购物
Description
硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买s
i的价值的东西。请问每次有多少种付款方法。
Input
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000
Output
每次的方法数
Sample Input
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900
Sample Output
4
27
Solution
很巧妙的思想。
首先不考虑限制,那么做一遍完全背包就行了。
若只有第一个货币有限制,设只能用\(d\)个,面值为\(c\),那么买\(s\)元钱的方案就是\(f[s]-f[s-c\cdot (d+1)]\),其中\(f[i]\)是完全背包的东西,即不考虑限制的方案。
因为现在只能用\(d\)张\(c\)元的钞票,那么一个比较显然的想法就是,由于所有超过\(d\)的方案都是不合法的,不妨用总体减去不合法的方案。
那么扩展到所有货币都有限制也就很显然了,由于货币种类较小,直接暴力容斥就好了。
时间复杂度\(O(s+2^4\cdot tot)\)。
#include<bits/stdc++.h>
using namespace std;
#define int long long
void read(int &x) {
x=0;int f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
void print(int x) {
if(x<0) putchar('-'),x=-x;
if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}
const int maxn = 1e5+10;
int c[5],tot,f[maxn],d[5];
signed main() {
for(int i=1;i<=4;i++) read(c[i]);read(tot);
f[0]=1;
for(int i=1;i<=4;i++)
for(int j=0;j+c[i]<maxn;j++)
f[j+c[i]]+=f[j];
for(int w=1;w<=tot;w++) {
int mx,ans=0;
for(int i=1;i<=4;i++) read(d[i]);read(mx);
for(int s=0;s<(1<<4);s++) {
int a=mx;
for(int i=1;i<=4;i++)
if(s&(1<<(i-1))) a-=c[i]*(d[i]+1);
if(a<0) continue;
ans+=(__builtin_popcount(s)&1?-1:1)*f[a];
}write(ans);
}
return 0;
}