HAOI2008 硬币购物

题目传送门

据说\(NOIp\)前写题解会\(\mathcal{RP}\)++


看数据范围,肯定不能写多重背包,会\(T\)飞~
如果每种硬币没有个数限制,就可以用完全背包了。

正难则反,我们可以先用完全背包预处理,然后减去不合法的情况。不合法的情况就是一个\(s-(d+1) \times c\)的背包
但如果我们直接减去,会导致重复计算。比如我们减去第一种超的和第二种超的,第一种和第二种都超的就被减了两次。所以要在容斥一波

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long ans,f[100010],c[5],d[5];
int read(){
    int k=0; char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9')
      k=k*10+c-48,c=getchar();
    return k;
}
int main(){
    for(int i=0;i<=3;i++) c[i]=read();
    f[0]=1LL;  //完全背包预处理
    for(int i=0;i<=3;i++)
      for(int j=c[i];j<=100001;j++)
        f[j]+=f[j-c[i]];
    int t=read();
    while(t--){
        for(int i=0;i<=3;i++) d[i]=read();
        int s=read(); ans=f[s];
        for(int i=1;i<=15;i++){  //用子集枚举来容斥
            int flag=0,num=s;
            for(int j=0;j<=3;j++)
              if(i&(1<<j))
                num-=(d[j]+1)*c[j],++flag; //减去不合法情况
            if(num>=0)  //容斥:奇减偶加
              if(flag&1) ans-=f[num];
              else ans+=f[num];
        }
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wxl-Ezio/p/9929816.html