Label
基本多重集组合数问题+完全背包
Description
给定4种面值的硬币,第 i i i种的面值是 C i C_i Ci。 n ( n ≤ 1 0 3 ) n(n\le 10^3) n(n≤103)次询问,每次询问给出每种硬币的持有数量 D i D_i Di与价格 S ( S ≤ 1 0 5 ) S(S\le 10^5) S(S≤105),问用给出硬币付款的方案总数。
solution
若用分组背包做此题,复杂度为 O ( 4 n S ) O(4nS) O(4nS),实际运行时会超时,故考虑复杂度更低的解法。
借用求多重集组合数的思路,原问题即转化为:给定不定方程 ∑ i = 1 4 C i x i = S \sum_{i=1}^4C_ix_i=S ∑i=14Cixi=S与 4 4 4个约束条件 x i ≤ D i x_i\le D_i xi≤Di,求该不定方程的非负整数解的个数。
采用同样的容斥方式,最后每组询问便转化为求 2 4 − 1 2^4-1 24−1次无限制的不定方程 ∑ i = 1 4 C i x i = S − ∑ i = 1 4 C a i ( D a i + 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 = 1 k ∁ U S a i ∣ |\bigcap_{i=1}^{k}\complement_US_{a_i}| ∣⋂i=1k∁USai∣无法利用排列组合直接推通式,但其实求 ∑ i = 1 4 C i x i = S − ∑ i = 1 4 C a i ( D a i + 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)的解的个数即为用***所有种类***的无限个 C i C_i Ci***恰好***装满容量为 S − ∑ i = 1 4 C a i ( D a i + 1 ) S-\sum_{i=1}^{4}C_{a_i}(D_{a_i}+1) S−∑i=14Cai(Dai+1)的背包的方案数。
我们利用完全背包
O
(
4
n
)
O(4n)
O(4n)预处理出利用不限制个数的四种硬币凑得值为
n
n
n的方案数
f
[
n
]
f[n]
f[n],对于每一组询问,答案即为:
f
[
n
]
+
∑
k
=
1
n
(
−
1
)
k
∑
a
i
<
a
i
+
1
∣
a
∣
=
k
f
[
S
−
∑
i
=
1
k
C
a
i
(
D
a
i
+
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 ( 2 4 ) O(2^4) O(24)求解即可。算法总复杂度 O ( 4 n + 2 4 n ) 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;
}