题目描述
硬币购物一共有 444 种硬币。面值分别为 c1,c2,c3,c4c_1,c_2,c_3,c_4c1,c2,c3,c4。某人去商店买东西,去了 tottottot 次。每次带 dijd_{ij}dij 枚 cijc_{ij}cij 硬币,买 sis_isi 的价值的东西。请问每次有多少种付款方法。di,s≤100000d_i,s\le 100000di,s≤100000,tot≤1000tot\le 1000tot≤1000。
算法分析
并不会多重背包,有空再去学,然而多重背包貌似也不能做。
先考虑没有限制的情况,就是一个完全背包,设已计算出的用前 nnn 种硬币购买价值 iii 的物品的方案数为 f[i]f[i]f[i],则仅给第 jjj 种硬币加上选取个数满足 [0,di,j][0,d_{i,j}][0,di,j] 限制时的方案数为 f[i]−f[i−c[j]×(d[j]+1)]f[i]-f[i-c[j]\times(d[j]+1)]f[i]−f[i−c[j]×(d[j]+1)]。
为什么呢?给第 jjj 种硬币加上这种限制后使用该种硬币的数量不得超过 d[j]d[j]d[j],考虑补集转化,我们可以先让它先不使用 d[j]+1d[j]+1d[j]+1 枚该种硬币,最后再使用 d[j]+1d[j]+1d[j]+1 枚该种金币,即 f[i−c[j]×(d[j]+1)]f[i-c[j]\times(d[j]+1)]f[i−c[j]×(d[j]+1)],这样无论之前怎么选都能保证选择的硬币个数大于 d[i]d[i]d[i],最后再用总的方案数减去不满足条件的方案数就是给第 jjj 种硬币加上限制的方案数。
注意到,如果我们给多种硬币加上限制,可能会有重复减去的部分,即该方案既满足使用硬币 xxx 的数量大于 d[x]d[x]d[x] 又满足使用硬币 yyy 的数量大于 d[y]d[y]d[y],它的方案数等于 f[i−c[x]×(d[x]+1)−c[y]×(d[y]+1)]f[i-c[x]\times(d[x]+1)-c[y]\times(d[y]+1)]f[i−c[x]×(d[x]+1)−c[y]×(d[y]+1)],将多减的部分加回去就可以了。这实际上使用的是容斥原理,可以用位运算的方法方便的计算出来。
注意要开 646464 位整数保存结果。
代码实现
#include <cstdio>
typedef long long int ll;
int c[4],d[4];ll f[100005],sub[4];
int main() {
f[0]=1;
for(int i=0;i<4;++i) {
scanf("%d",&c[i]);
for(int j=c[i];j<=100000;++j) f[j]+=f[j-c[i]];
}
int tot;scanf("%d",&tot);
while(tot--) {
for(int i=0;i<4;++i) {
scanf("%d",&d[i]);
sub[i]=c[i]*(d[i]+1LL);
}
int s;scanf("%d",&s);ll ans=0;
for(int i=0;i<(1<<4);++i) {
int cnt=0;ll t=0;
for(int j=0;j<4;++j) if(i>>j&1) {
++cnt;t+=sub[j];
}
if(s-t>=0) (cnt&1)?ans-=f[s-t]:ans+=f[s-t];
}
printf("%lld\n",ans);
}
return 0;
}