无限元素多重集组合
对于一个含有 \(k\) 种元素的多重集 \(S = \{a_1 \cdot \infty, a_2 \cdot \infty, \cdots, a_k \cdot \infty \}\),从当中取出 \(r\) 个元素进行组合,共有 \[ \binom{r + k - 1}{k - 1} = \binom{r + k - 1}{r} \]
种取法,这个结论用隔板法易证:
\[ \overbrace {\underbrace {\times \cdots \times \times | \times \times \cdots | \times \times}_{r 个元素 \times}}^{k -1 个隔板 |} \]
有限元素多重集组合
对于一个含有 \(k\) 种元素共计 \(n\) 个的多重集 \(S = \{a_1 \cdot n_1, a_2 \cdot n_2, \cdots, a_k \cdot n_k \}\),从当中取出 \(r\) 个元素进行组合,方案数为: \[ \sum_{X \subseteq \{1, 2, 3, \cdots, k\}} (-1)^{|X|} \binom{r - \sum_{i \in X}(n_i + 1) + k - 1}{k - 1} \] 证明:
如果没有数量限制那么答案显然是 \(\binom{r + k - 1}{k - 1}\),但是由于数量有限,我们要排除 \(a_i\) 多于 \(n_i\) 的方案,令 \(P_i\) 表示第 \(i\) 种元素选了至少 \(n_i + 1\) 个的方案集合。要构造这样的方案,我们只需要先选出 \(n_i + 1\) 个该种元素,再在剩下的集合中选出 \(r - n_i - 1\) 个即可,于是我们有 \[ |P_i| = \binom{r - (n_i + 1) + k - 1}{k - 1} \] 所有所有不合法的方案可以表示为
\[ \bigcup_{1 \le i \le k} P_i \] 接下来结合容斥原理易证。
例子:CF451E
就是以上公式的应用,对于容斥原理使用递归的写法,求取组合数时使用了 Lucas 定理加以优化,代码很短:
#include <cstdio>
using namespace std;
typedef long long ll;
const ll P = 1000000007;
const int N = 22;
, m, inv[N], a[N];
ll n
(ll a, ll b) {
ll binomif (a < 0 || b < 0 || a < b) return 0;
%= P;
a if (a == 0 || b == 0) return 1;
= 1;
ll ret for (ll i = a; i > a - b; i--)
= ret * i % P;
ret for (ll i = 1; i <= b; i++)
= ret * inv[i] % P;
ret return ret;
}
(int d, ll x) {
ll calcif (d == 0) return x == 0 ? 0 : binom(n + m - 1 - x, n - 1);
else return (calc(d - 1, x) - calc(d - 1, x + a[d] + 1)) % P;
}
void setup() {
[1] = 1;
invfor (int i = 2; i <= n; i++)
[i] = (-(P / i) * inv[P % i] % P + P) % P;
inv}
int main() {
("%lld%lld", &n, &m);
scanf();
setupfor (int i = 1; i <= n; i++)
("%lld", &a[i]);
scanf= binom(n + m - 1, n - 1) + calc(n, 0);
ll ans ("%lld\n", (ans % P + P) % P);
printfreturn 0;
}