题目
P1 背包
子集积 > m >m >m 个数并不好求,考虑子集积 ≤ m \le m ≤m 的个数 x x x,答案即为 ( 2 n − x ) (2^n - x) (2n−x)。
对于子集积 ≤ m \le m ≤m 的个数,可以化为 0-1 背包问题做, f i , j f_{i,j} fi,j 表示前 i i i 个数,子集积为 j j j 的个数,有:
f i , j = ∑ j = 1 m f i − 1 , j a i f_{i,j}=\sum \limits_{j=1}^{m} f_{i-1,\frac {j} {a_i}} fi,j=j=1∑mfi−1,aij ( j j j 是 a i a_i ai 的倍数)。
背包问题常规地去掉一维: f j f_j fj 表示子集积为 j j j 的个数:
f j = ∑ j = 1 m f j a i f_j=\sum \limits_{j=1}^{m} f_{\frac {j} {a_i}} fj=j=1∑mfaij ( j j j 是 a i a_i ai 的倍数)。
cin >> n >> m;for(int i=1; i<=n; i++) cin >> a[i];f[1] = 1;for(int i=1; i<=n; i++)for(int j=(m / a[i]) * a[i]; j>=a[i]; j-=a[i])f[j] += f[j / a[i]], f[j] %= mod;int sum = qpow(2, n);for(int i=1; i<=m; i++)sum -= f[i], sum = ((sum % mod) + mod) % mod;cout << sum;
时间复杂度 O ( n × ∑ i = 1 n m a i ) O(n \times \sum\limits_{i=1}^{n} {\frac {m} {a_i}}) O(n×i=1∑naim) ,最坏情况下 O ( n m ) O(nm) O(nm) 。
P2 优化
优化 1
若序列中有 100 100 100 个 1 1 1 ,然而任意多个 1 1 1 不会对子集积产生影响,我们只需要在方案数中乘以 2 100 2^{100} 2100 即可。
...int sum = qpow(2, n);for(int i=1; i<=m; i++)sum -= (f[i] * qpow(2, cnt[1])) % mod, sum = ((sum % mod) + mod) % mod;cout << sum;
优化 2
时间复杂度高的原因在于重复的计算:若有 100 100 100 个 2 2 2 ,我们会将第 2 , 3 2,3 2,3 个 2 2 2 、第 3 , 4 3,4 3,4 个 2 2 2 算了两次。我们应该只关心是几个 2 2 2 ,而不关心是哪几个 2 2 2。
对于任意一个数 x x x ,设其出现了 t t t 次,我们可以对 x 1 , x 2 , . . . , x t x^1,x^2,...,x^t x1,x2,...,xt 分别计算,使用 x i x^i xi 计算贡献时乘以 C t i C_{t}^i Cti, 即 :
f j = ∑ i = 1 t ( f j x i × C t i ) f_j=\sum\limits_{i=1}^{t} ( f_{\frac {j} {x^i}} \times C_t^i) fj=i=1∑t(fxij×Cti) ( j j j 是 x k x^k xk 的倍数)。
时间复杂度 O ( n ∑ i = 1 n ( log a i m ) ) O(n \sum\limits_{i=1}^{n} (\log_{a_i}{m})) O(ni=1∑n(logaim)),最坏情况下 O ( n log m ) O(n \log m) O(nlogm)。
注意: 这里与多重背包的二进制拆分拆成多个物品不同,而是优化了对于一个物品的计算方式。
代码