1. 问题描述:
Devu 有 N 个盒子,第 i 个盒子中有 Ai 枝花。同一个盒子内的花颜色相同,不同盒子内的花颜色不同。Devu 要从这些盒子中选出 M 枝花组成一束,求共有多少种方案。若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。结果需对 10 ^ 9 + 7 取模之后方可输出。
输入格式
第一行包含两个整数 N 和 M。第二行包含 N 个空格隔开的整数,表示 A1,A2,…,AN。
输出格式
输出一个整数,表示方案数量对 10 ^ 9 + 7 取模后的结果。
数据范围
1 ≤ N ≤ 20,
0 ≤ M ≤ 10 ^ 14,
0 ≤ Ai ≤ 10 ^ 12
输入样例:
3 5
1 3 2
输出样例:
3
来源:https://www.acwing.com/problem/content/216/
2. 思路分析:
这道题目属于容斥原理中一道很经典的题目,我们先考虑一下理想的情况,每个盒子中的花有无限个,我们需要求解出N个盒子中选择M朵花的方案数目,这个与不定方程中正整数解得题目是一样的,我们先可以做一下映射,令yi = xi + 1,这样yi >= 1,相当于是给我们N + M个小球,有N + M - 1个空隙,N - 1个隔板,求解总共的方案数目,使用隔板法我们知道最终的方案数目为CN+M-1N-1
而原问题是有限制的,x1 <= A1,x2 <= A2,...xN <= AN,怎么样通过没有限制的情况拼凑出有限制的情况呢?这个时候就需要使用到容斥原理了,正向求解不好求解那么就反向求解,我们可以使用没有限制条件下总的方案数目减去不满足条件的方案数目那么就是答案了:
3. 代码如下:
class Solution:
# 快速幂
def quickPower(self, a: int, b: int, p: int):
res = 1
while b > 0:
if b & 1:
res = res * a % p
a = a * a % p
b >>= 1
return res
# 使用公式计算组合数
def C(self, a: int, b: int, mod: int, down: int):
if a < b: return 0
up = 1
for i in range(a, a - b, -1):
up = i % mod * up % mod
return up * down % mod
def process(self):
# n个盒子, 总共要选出m枝花
n, m = map(int, input().split())
# A存储每一个盒子中花的数量
A = list(map(int, input().split()))
res, mod = 0, 10 ** 9 + 7
down = 1
for i in range(1, n):
down = down * i % mod
# 先预处理出down!的阶乘, 而且down!远小于mod所以可以使用费马小定理求解出逆元
down = self.quickPower(down, mod - 2, mod)
# 枚举2 ^ n个状态, 计算其中1的数目
for i in range(1 << n):
a, b = m + n - 1, n - 1
# 容斥原理中奇数项是负数, 偶数项是整数, 所以在枚举二进制状态的时候计算出1的数目那么就是最终的符号
sign = 1
for j in range(n):
if i >> j & 1:
sign *= -1
# 减去对应的1的数目
a -= A[j] + 1
res = (res + self.C(a, b, mod, down) * sign) % mod
return res
if __name__ == "__main__":
print(Solution().process())