bzoj1042 [HAOI2008]硬币购物
Description
硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。
Input
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000
Output
每次的方法数
背包预处理+容斥
最近学了学容斥原理,感觉这类题都好难想
可能做多了就好了,刚学dp的时候也是这样
先考虑如果没有每种硬币数量的限制的情况,那么就是一个完全背包
所以可以\(O(s)\)预处理出\(f_i\)表示价值为\(i\)时,用任意多的硬币,有几种方案
然后在把限制加进去,要用到容斥
答案为\(f_i\)减去有一种硬币的数量超限,加上有两种硬币的数量超限,减去有三种硬币的数量超限,加上有四种硬币的数量超限
那么考虑如何得到每一种硬币数量超限的方案数
设这是第\(i\)种,想让它超限,就至少选\(d_i+1\)个这种硬币
那么它的价值就是\(c_i\times (d_i+1)\),剩下的价值就是\(s-c_i\times (d_i+1)\)
所以方案数是\(f_{c_i\times (d_i+1)}\),当然如果\(s<c_i\times (d_i+1)\),方案数肯定为0
所以询问复杂度\(O(1)\)
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
int x=0,y=1;
char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
int n;
int c[5],d[5],tmp[5];
LL f[100006];
int main(){
c[1]=read();c[2]=read();c[3]=read();c[4]=read();n=read();
f[0]=1;
for(reg int j=1;j<5;j++)
for(reg int i=1;i<=1e5;i++)
if(i>=c[j]) f[i]+=f[i-c[j]];
while(n--){
d[1]=read();d[2]=read();d[3]=read();d[4]=read();
int s=read();
LL ans=f[s];
for(reg int i=1;i<5;i++) tmp[i]=(d[i]+1)*c[i];
for(reg int i=1;i<5;i++)
if(s>=tmp[i]) ans-=f[s-tmp[i]];
for(reg int i=1;i<5;i++)
for(reg int j=i+1;j<5;j++)
if(s>=tmp[i]+tmp[j])
ans+=f[s-tmp[i]-tmp[j]];
if(s>tmp[1]+tmp[2]+tmp[3]) ans-=f[s-tmp[1]-tmp[2]-tmp[3]];
if(s>tmp[1]+tmp[3]+tmp[4]) ans-=f[s-tmp[1]-tmp[3]-tmp[4]];
if(s>tmp[2]+tmp[3]+tmp[4]) ans-=f[s-tmp[2]-tmp[3]-tmp[4]];
if(s>tmp[1]+tmp[2]+tmp[4]) ans-=f[s-tmp[1]-tmp[2]-tmp[4]];
if(s>tmp[1]+tmp[2]+tmp[3]+tmp[4]) ans+=f[s-tmp[1]-tmp[2]-tmp[3]-tmp[4]];
std::printf("%lld\n",ans);
}
return 0;
}