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/

posted @ 2023-02-22 15:33  onlyblues  阅读(27)  评论(0编辑  收藏  举报
Web Analytics