Devu和鲜花
Devu和鲜花
Devu 有 $N$ 个盒子,第 $i$ 个盒子中有 $A_i$ 枝花。
同一个盒子内的花颜色相同,不同盒子内的花颜色不同。
Devu 要从这些盒子中选出 $M$ 枝花组成一束,求共有多少种方案。
若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。
结果需对 $10^9+7$ 取模之后方可输出。
输入格式
第一行包含两个整数 $N$ 和 $M$。
第二行包含 $N$ 个空格隔开的整数,表示 $A_1,A_2, \dots,A_N$。
输出格式
输出一个整数,表示方案数量对 $10^9+7$ 取模后的结果。
数据范围
$1 \leq N \leq 20$,
$0 \leq M \leq {10}^{14}$,
$0 \leq A_i \leq {10}^{12}$
输入样例:
3 5 1 3 2
输出样例:
3
解题思路
这题是用容斥原理来做的。
先考虑每个盒子中花的数量为无限个,假设$x_i$表示在第$i$个盒子中选择花的数量,由于要从$n$个盒子中选择$m$枝花,因此有$x_1 + x_2 + \ldots + x_n = m$,其中$x_i \geq 0$。最终选法的个数就是这个方程的非负整数解的个数。这个可以用隔板法来求,由于在隔板法中要求变量的值为正整数,因此还需要对这个方程做一个映射。设$y_i = x_i + 1$,因此有$y_i \geq 1$,等式就变成了$y_1 + y_2 + \ldots + y_n = m + n$,因此正整数解的方案数就是$C_{m+n-1}^{n-1}$。
在原问题中每个盒子选择花的数量是有限制的,即要求$x_i \leq a_i$,而上面的做法是没有限制的情况(即总方案数),为了通过没有限制的情况得到有限制的情况,这里就可以用容斥原理了。由于有$n$个条件要同时满足,我们可以求补集,即求至少有一个条件不满足的方案数量,然后用总的方案数量$C_{m+n-1}^{n-1}$减去至少有一个条件不满足的方案数量,得到的结果就是同时满足$n$个条件的数量。
如果第$i$个条件不满足,那么就是$x_i \geq a_i + 1$。设集合$S_i$表示不满足第$i$个条件的方案,因此同时满足$n$个条件的方案数就是$$C_{m+n-1}^{n-1} - \left| \bigcup\limits_{i=1}^{n}{S_i} \right|$$
先分析一下$\left| S_1 \right|$如何求。第$1$个盒子至少要拿$a_i + 1$枝花,那么我们先从第$1$个盒子取出$a_1 + 1$枝花,此时问题就变成了从$n$个盒子中选择$m - (a_1 + 1)$枝花。为什么还要从第$1$个盒子取花?这是因为第$1$个盒子至少要取$a_1 + 1$枝花,因此第$1$个盒子还可以选,先取出$a_1 + 1$枝花,后面不管从第$1$个盒子取多少花最终都至少从第$1$个盒子取出超过$a_1$枝花。因此有$\left| S_1 \right| = C_{m - (a_1 + 1) +n-1}^{n-1}$。
因此$\left| S_i \right| = C_{m - (a_i + 1) +n-1}^{n-1}$,那么$\left| S_i \cap S_j \right|$呢?同理先从第$i$个盒子取出$a_i + 1$枝花,再从第$j$个盒子取出$a_j + 1$枝花,问题就变成了从$n$个盒子中选择$m - (a_i + 1) - (a_j + 1)$枝花,方案数就是$\left| S_i \cap S_j \right| = C_{m - (a_i + 1) - (a_j + 1) +n-1}^{n-1}$ 。以此类推来求$\left| S_i \cap S_j \cap S_k \cap \cdots \right|$的方案数。
因此可以通过二进制枚举来实现求容斥原理的过程。
其中由于$n$最大只有$20$,因此公式中的组合数$C_{m+n-1}^{n-1}$直接按照定义$\dfrac{(m+n-1) \cdot (m+n-2) \cdots (m+1)}{(n-1) !}$来算就可以了。除法用乘法逆元,由于取模的数是质数,$n$最大取到$20$,因此$(n-1)!$必然与$10^9+7$互质,因此可以用费马小定理来求逆元。注意到公式中的组合数都是取出$n-1$个数,即分母都是$(n-1)!$,因此可以先预处理出$(n-1)!$的逆元,这样每次求组合数就不需要重复计算了。
AC代码如下,时间复杂度为$O(n \cdot 2^n)$:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 30, mod = 1e9 + 7; 7 8 LL p[N]; 9 LL down = 1; // (n-1)!的逆元 10 11 int qmi(int a, int k) { 12 int ret = 1; 13 while (k) { 14 if (k & 1) ret = 1ll * ret * a % mod; 15 a = 1ll * a * a % mod; 16 k >>= 1; 17 } 18 return ret; 19 } 20 21 int C(LL a, LL b) { // 从a个里面选b个 22 if (a < b) return 0; 23 int up = 1; // 分子 24 for (LL i = a; i > a - b; i--) { 25 up = i % mod * up % mod; 26 } 27 return up * down % mod; // 分子乘以分母的逆元 28 } 29 30 int main() { 31 LL n, m; 32 scanf("%lld %lld", &n, &m); 33 for (int i = 0; i < n; i++) { 34 scanf("%lld", p + i); 35 } 36 for (int i = 1; i < n; i++) { 37 down = down * i % mod; 38 } 39 down = qmi(down, mod - 2); // 求分母的逆元 40 int ret = C(m + n - 1, n - 1); 41 for (int i = 1; i < 1 << n; i++) { 42 LL a = m + n - 1, b = n - 1, cnt = 0; 43 for (int j = 0; j < n; j++) { 44 if (i >> j & 1) a -= p[j] + 1, cnt ^= 1; 45 } 46 if (cnt) ret = (ret - C(a, b)) % mod; 47 else ret = (ret + C(a, b)) % mod; 48 } 49 printf("%d", (ret + mod) % mod); 50 51 return 0; 52 }
参考资料
AcWing 214. Devu和鲜花(算法提高课):https://www.acwing.com/video/739/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17144554.html