bzoj 1042: [HAOI2008]硬币购物

题目链接

bzoj1042

题解

如果没有个数限制就是一个完全背包
考虑利用全集减去超出限制的种数
利用容斥
减去一种金币超出的,加上两种金币超出的,减去三种.......

\(f(S)\)只有 S种金币超出的方案数,\(g(S)\)为S中的金币超过方限制,其他随意的方案数
那么\(\sum_{T\supseteq S}f \left(T \right)\)
我们要求\(f\left( \emptyset \right)\)
求g(s),吧\(N\)中减去\(S\)中选了\(d_i+1\)个的和,然后剩下的就是一个完全背包
预处理后容斥查询

code

#include<cstdio>

inline int read() {
    int x=0,f=1;
    char c=getchar() ;
    while(c<'0'||c>'9') {
         if(c=='-')f=-1;
         c=getchar();
    }
    while(c<='9'&&c>='0') {
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}const int maxn = 100001;
int M[6],N[6];
int f[maxn];
int tot,ned;
void init() {
    f[0]=1;
    for(int i=1;i<=4;++i)
        for(int j=M[i];j<maxn;++j) 
            f[j]+=f[j-M[i]];
}
int ans;
void solve(int rem,int cnt,int num) {
    if(num==5) {
        if(!cnt)return ;
        if(rem<0)return ;
        if(cnt&1) ans-=f[rem];
        else ans+=f[rem];
        return ;
    }
    solve(rem-((N[num]+1)*M[num]),cnt+1,num+1);
    solve(rem,cnt,num+1);
}
int main() {
    for(int i=1;i<5;++i) M[i]=read();
    tot=read();
    init();
    for(;tot--;) {
        for(int i=1;i<5;++i) N[i]=read();
        ned=read();ans=f[ned];
        solve(ans,0,1);
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2018-02-03 11:17  zzzzx  阅读(...)  评论(... 编辑 收藏