[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;
}
posted @ 2019-02-14 10:12  Hyscere  阅读(...)  评论(... 编辑 收藏