题目描述
硬币购物一共有 4 4 4 种硬币。面值分别为 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4。某人去商店买东西,去了 t o t tot tot 次。每次带 d i j d_{ij} dij 枚 c i j c_{ij} cij 硬币,买 s i s_i si 的价值的东西。请问每次有多少种付款方法。 d i , s ≤ 100000 d_i,s\le 100000 di,s≤100000, t o t ≤ 1000 tot\le 1000 tot≤1000。
算法分析
并不会多重背包,有空再去学,然而多重背包貌似也不能做。
先考虑没有限制的情况,就是一个完全背包,设已计算出的用前 n n n 种硬币购买价值 i i i 的物品的方案数为 f [ i ] f[i] f[i],则仅给第 j j j 种硬币加上选取个数满足 [ 0 , d i , 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)]。
为什么呢?给第 j j j 种硬币加上这种限制后使用该种硬币的数量不得超过 d [ j ] d[j] d[j],考虑补集转化,我们可以先让它先不使用 d [ j ] + 1 d[j]+1 d[j]+1 枚该种硬币,最后再使用 d [ j ] + 1 d[j]+1 d[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],最后再用总的方案数减去不满足条件的方案数就是给第 j j j 种硬币加上限制的方案数。
注意到,如果我们给多种硬币加上限制,可能会有重复减去的部分,即该方案既满足使用硬币 x x x 的数量大于 d [ x ] d[x] d[x] 又满足使用硬币 y y y 的数量大于 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)],将多减的部分加回去就可以了。这实际上使用的是容斥原理,可以用位运算的方法方便的计算出来。
注意要开 64 64 64 位整数保存结果。
代码实现
#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;
}