Label
基本多重集组合数问题+完全背包
Description
给定4种面值的硬币,第iii种的面值是CiC_iCi。n(n≤103)n(n\le 10^3)n(n≤103)次询问,每次询问给出每种硬币的持有数量DiD_iDi与价格S(S≤105)S(S\le 10^5)S(S≤105),问用给出硬币付款的方案总数。
solution
若用分组背包做此题,复杂度为O(4nS)O(4nS)O(4nS),实际运行时会超时,故考虑复杂度更低的解法。
借用求多重集组合数的思路,原问题即转化为:给定不定方程∑i=14Cixi=S\sum_{i=1}^4C_ix_i=S∑i=14Cixi=S与444个约束条件xi≤Dix_i\le D_ixi≤Di,求该不定方程的非负整数解的个数。
采用同样的容斥方式,最后每组询问便转化为求24−12^4-124−1次无限制的不定方程∑i=14Cixi=S−∑i=14Cai(Dai+1)\sum_{i=1}^4C_ix_i=S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1)∑i=14Cixi=S−∑i=14Cai(Dai+1)的值。
与原问题不同的是,此时求∣⋂i=1k∁USai∣|\bigcap_{i=1}^{k}\complement_US_{a_i}|∣⋂i=1k∁USai∣无法利用排列组合直接推通式,但其实求∑i=14Cixi=S−∑i=14Cai(Dai+1)\sum_{i=1}^4C_ix_i=S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1)∑i=14Cixi=S−∑i=14Cai(Dai+1)的解的个数即为用***所有种类***的无限个CiC_iCi***恰好***装满容量为S−∑i=14Cai(Dai+1)S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1)S−∑i=14Cai(Dai+1)的背包的方案数。
我们利用完全背包O(4n)O(4n)O(4n)预处理出利用不限制个数的四种硬币凑得值为nnn的方案数f[n]f[n]f[n],对于每一组询问,答案即为:
f[n]+∑k=1n(−1)k∑ai<ai+1∣a∣=kf[S−∑i=1kCai(Dai+1)]f[n]+\sum_{k=1}^{n}(-1)^k\sum_{a_i<a_{i+1}}^{|a|=k}f[S-\sum_{i=1}^kC_{a_i}(D_{a_i}+1)]f[n]+k=1∑n(−1)kai<ai+1∑∣a∣=kf[S−i=1∑kCai(Dai+1)]
对于每组询问只需O(24)O(2^4)O(24)求解即可。算法总复杂度O(4n+24n)O(4n+2^4n)O(4n+24n)。
Code
#include<cstdio>
#include<iostream>
#define ri register int
#define ll long long
using namespace std;
const int MAXN=1e5;
int N=4,c[5],T,d[5],S,tot,cnt,use[5];
ll f[MAXN+20],ans;
void dp()
{
f[0]=1;
for(ri i=1;i<=4;++i)
for(ri v=c[i];v<=MAXN;++v) f[v]+=f[v-c[i]];
}
void inex()
{
for(ri i=1;i<=N;++i) scanf("%d",&d[i]);
scanf("%d",&S);
ans=f[S];
for(ri s=1;s<(1<<N);++s)
{
cnt=0,tot=0;
for(ri i=1;i<=N;++i)
if(s&(1<<(i-1)))
++cnt,tot+=(d[i]+1)*c[i];
if(S-tot<0) continue;
if(cnt&1) ans-=f[S-tot];
else ans+=f[S-tot];
}
cout<<ans<<'\n';
}
int main()
{
for(ri i=1;i<=N;++i) scanf("%d",&c[i]);
dp();
scanf("%d",&T);
for(ri i=1;i<=T;++i) inex();
return 0;
}