组合数学
laihaochen · · 个人记录
目录
1. 排列数与组合数
2. 组合数的求法
3. 组合数的应用
4. Lucas定理
5. Catalan数
6. 其他的组合问题
by laihaochen
1. 排列数与组合数
排列数
从
组合数
从
性质
-
C_n^m = C_n^{n - m} -
C_n^m = C_{n - 1}^m + C_{n - 1}^{m - 1} -
C_n^0 + C_n^1 + C_n^2 + …… + C_n^n = 2^n
证明:
性质1.
性质2. 在
不选第
选第
故共有
所以,
性质3. 从
选
选
……
选
另一方面,每个元素都有选或不选两种可能,故有
所以,
2. 组合数的求法
-
根据性质2,可以用
DP 来求组合数,时间复杂度为O(n^2) -
如果题目要求输出
C_n^m \pmod p ,那么可以先求出n! \pmod p 取模的值,在求出m!(n - m)! \pmod p 的逆元,乘起来得到C_n^m \pmod p ,时间复杂度为O(n) -
用
O(n) 预处理出阶乘(jc_i) 和阶乘的逆元(inv_i) ,就可以只用O(1) 的时间算出1 \leq y \leq x \leq n 的所有组合数C_x^y \equiv jc_x * inv_y * inv_{x - y} \pmod p -
Lucas定理 + 3. 后面会介绍
3. 组合数的应用
二项式定理
(a + b)^n = \sum_{k = 0}^n C_n^ka^kb^{n - k}
多重集组合数
设
证明:
原命题等价于统计下列集合的数量:
因为
所以
因为
P4778 Counting swaps
我们把题目所给的排列
引理:
把一个大小为
证明:
用数学归纳法:首先,把长度为
假设
例如:
变为
两个环的大小分别为
设
由上面的证明过程可知,每次要把大小为
\begin{array}{rcl} x = y, T(x, y) = \frac{n}{2}\\ x \neq y,T(x, y) = n\\ \end{array} \right.
则由多重集排列数可得,
同样由多重集排列数可得,
这样就可以完成
打表可得
//O(n^2)
/*#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, mod = 1e9 + 9;
long long n, ans, p[N], cnt[N], S[N], f[N], jc[N];
long long T (long long x, long long y) {
if (x != y) {
return x + y;
}
return x;
}
long long fp (long long a, long long b) {
long long res = 1;
while (b > 0) {
if (b & 1) {
res = (res * a) % mod;
}
b >>= 1;
a = (a * a) % mod;
}
return res % mod;
}
long long C (long long x, long long y) {
return ((jc[x + y - 2] * fp(jc[x - 1], mod - 2)) % mod * fp(jc[y - 1], mod - 2)) % mod;
}
int getf (int x) {
if (x == S[x]) {
return x;
}
return S[x] = getf(S[x]);
}
void merge (int x, int y) {
int u = getf(x), v = getf(y);
if (u != v) {
S[u] = v;
}
}
int main() {
jc[0] = 1;
for (long long i = 1; i <= 1000; i++) {
jc[i] = (jc[i - 1] * i) % mod;
}
f[1] = 1;
for (long long i = 2; i <= 1000; i++) {
for (long long x = 1; x <= i / 2; x++) {
long long y = i - x;
f[i] = (f[i] + (( (T(x, y) * ( (f[x] * f[y]) % mod )) % mod) * C(x, y)) % mod) % mod;
}
}
int t;
scanf ("%d", &t);
while (t--) {
memset (cnt, 0, sizeof(cnt));
scanf ("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf ("%lld", &p[i]);
S[i] = i;
}
for (int i = 1; i <= n; i++) {
merge (p[i], i);
}
for (int i = 1; i <= n; i++) {
cnt[getf(i)]++;
}
ans = 1;
long long k = 0;
for (int i = 1; i <= n; i++) {
if (cnt[i] == 0) {
continue;
}
ans = (ans * f[cnt[i]]) % mod;
ans = (ans * fp (jc[cnt[i] - 1], mod - 2)) % mod;
k++;
}
ans = (ans * jc[n - k]) % mod;
printf ("%lld\n", ans);
}
return 0;
}*/
//打表可得f[i] = i^{i - 2}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, mod = 1e9 + 9;
long long n, ans, p[N], cnt[N], S[N], f[N], jc[N];
long long T (long long x, long long y) {
if (x != y) {
return x + y;
}
return x;
}
long long fp (long long a, long long b) {
long long res = 1;
while (b > 0) {
if (b & 1) {
res = (res * a) % mod;
}
b >>= 1;
a = (a * a) % mod;
}
return res % mod;
}
long long C (long long x, long long y) {
return ((jc[x + y - 2] * fp(jc[x - 1], mod - 2)) % mod * fp(jc[y - 1], mod - 2)) % mod;
}
int getf (int x) {
if (x == S[x]) {
return x;
}
return S[x] = getf(S[x]);
}
void merge (int x, int y) {
int u = getf(x), v = getf(y);
if (u != v) {
S[u] = v;
}
}
int main() {
jc[0] = 1;
for (long long i = 1; i < N; i++) {
jc[i] = (jc[i - 1] * i) % mod;
}
f[1] = 1;
for (long long i = 2; i < N; i++) {
f[i] = fp (i, i - 2);
}
int t;
scanf ("%d", &t);
while (t--) {
memset (cnt, 0, sizeof(cnt));
scanf ("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf ("%lld", &p[i]);
S[i] = i;
}
for (int i = 1; i <= n; i++) {
merge (p[i], i);
}
for (int i = 1; i <= n; i++) {
cnt[getf(i)]++;
}
ans = 1;
long long k = 0;
for (int i = 1; i <= n; i++) {
if (cnt[i] == 0) {
continue;
}
ans = (ans * f[cnt[i]]) % mod;
ans = (ans * fp (jc[cnt[i] - 1], mod - 2)) % mod;
k++;
}
ans = (ans * jc[n - k]) % mod;
printf ("%lld\n", ans);
}
return 0;
}
4. Lucas定理
若
p 是质数,则对于任意整数1 \leq m \leq n ,有:C_n^m \equiv C_{n \bmod p}^{m \bmod p} * C_{n / p}^{m / p} \pmod p
证明:由二项式定理可知
同理,对于方程
证毕。
利用
inline void init (long long p) {
jc[0] = 1;
for (int i = 1; i <= p; i++) {
jc[i] = (jc[i - 1] * i) % p;
}
inv[p] = 0;
inv[p - 1] = f (jc[p - 1], p - 2, p);
for (int i = p - 2; i >= 0; i--) {
inv[i] = (inv[i + 1] * (i + 1)) % p;
}
}
long long C (int n, int m, int p) {
if (m > n) {
return 0;
}
return (((jc[n] * inv[m]) % p) * inv[n - m]) % p;
}
long long Lucas (int n, int m, int p) {
if (m == 0) {
return 1;
}
return (Lucas (n / p, m / p, p) * C (n % p, m % p, p)) % p;
}
P2480 [SDOI2010]古代猪文
题目大意:给定
显然指数很大,我们求不了。所以我们想办法将指数取模。
因为
所以我们分别求出
注意用
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int mod1 = 999911659, mod2 = 999911658, N = 50005;
int n, g, cnt, factor[N];
long long jc[N], inv[N];
int p[10] = {0, 2, 3, 4679, 35617}, a[10];
long long f (long long a, long long b, long long k) {
long long res = 1;
while (b > 0) {
if (b & 1) {
res = (res * a) % k;
}
b >>= 1;
a = (a * a) % k;
}
return res % k;
}
void getfactor (int x) {
for (int i = 1; i <= sqrt(x); i++) {
if (x % i != 0) {
continue;
}
factor[++cnt] = i;
if (i != n / i) {
factor[++cnt] = x / i;
}
}
}
//求指数
inline void init (long long p) {
jc[0] = 1;
for (int i = 1; i <= p; i++) {
jc[i] = (jc[i - 1] * i) % p;
}
inv[p] = 0;
inv[p - 1] = f (jc[p - 1], p - 2, p);
for (int i = p - 2; i >= 0; i--) {
inv[i] = (inv[i + 1] * (i + 1)) % p;
}
}
long long C (int n, int m, int p) {
if (m > n) {
return 0;
}
return (((jc[n] * inv[m]) % p) * inv[n - m]) % p;
}
long long Lucas (int n, int m, int p) {
if (m == 0) {
return 1;
}
return (Lucas (n / p, m / p, p) * C (n % p, m % p, p)) % p;
}
long long calc (int x) {
init(p[x]);
for (int i = 1; i <= cnt; i++) {
a[x] = (a[x] + Lucas(n, factor[i], p[x])) % p[x];
}
}
//
long long crt () {
long long ans = 0, M = 1;
for (int i = 1; i <= 4; i++) {
M *= p[i];
}
for (int i = 1; i <= 4; i++) {
long long Mi = M / p[i];
long long Mi_1 = f(Mi, p[i] - 2, p[i]);
ans = ((ans + ((Mi * Mi_1) % M) * a[i]) % M + M) % M;
}
return ans % M;
}
int main() {
scanf ("%d%d", &n, &g);
if (g % mod1 == 0) {//当mod1 | g就不互质了,就不能用欧拉定理
printf ("0\n");
return 0;
}
getfactor (n);
for (int i = 1; i <= 4; i++) {
calc (i);
}
printf ("%lld\n", f(g, crt(), mod1));
return 0;
}
5. Catalan数
给定
Cat_n = \frac{C_{2n}^n}{n + 1}
证明:
假设
同理,
因此,以下两种序列的数量相等:
-
由
n 个0 与n 个1 组成的,存在一个前缀0 比1 多的序列。 -
由
n - 1 个0 与n + 1 个1 组成的序列。
显然,第2.种序列有
综上,由
又
更多
注:在不相交弦问题中,最顶端的点的顺时针方向的第一条线的左右括号写反了。
6. 其他的组合问题
6.1 小球
1.
n 个相同的小球,选m 个(n \geq m)
这个问题需要用
状态:
我们分类讨论,考虑是否有盒子中只有一个球:
- 如果有盒子中只有一个球:
那么我们把这个盒子去掉,剩下的有
- 如果没有盒子中只有一个球:
那么我们把所有盒子都去掉一个球,剩下的有
所以得出转移方程:
显然,边界是
目标就是
f[1][1] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m && j <= i; j++) {//注意这里还要让j <= i
f[i][j] = f[i - 1][j - 1] + f[i - j][j];
}
}
2.
n 个相同的球,放入m 个相同的盒子,盒子能为空
类似地,我们定义
这次我们来看有无空盒子:
- 如果有空盒子:
那么我们去掉这个盒子,剩下的有
- 如果无空盒子:
那么我们把所有的盒子中去掉一个球,剩下的有
所以得出转移方程:
显然,边界是
由于没有了
-
n < m
由于每个小球只能进入一个盒子,所以只有
-
n \geq m
我们还有另一种做法:我们用盒子与小球
3.
n 个相同的球,放入m 个不同的盒子,盒子不能为空(n \geq m)
5.
n 个不同的球,放入m 个相同的盒子,盒子不能为空(n \geq m)
定义:
我们考虑把第
- 放入一个新盒子:
- 放入放过球的盒子:
因为盒子不能为空,所以边界为:
答案:
6.
n 个不同的球,放入m 个相同的盒子,盒子能为空
我们可以转换为
7.
n 个不同的球,放入m 个不同的盒子,盒子不能为空(n \geq m)
我们可以转换为
8.
n 个不同的球,放入m 个不同的盒子,盒子能为空
我们可以发现每个球都有
6.3 圆排列(第一类斯特林数)
定义
分类讨论第
- 第
i 个元素放在了一个新的圆里:
- 第
i 个元素放在已有的某个元素后面:
故
边界:
其实这个
6.4 正难则反
在
n 个不同的球中选m 个球,不能选相邻的两个球,有多少种方案。(n \geq 2 * m - 1)
我们考虑剩下来的
显然,就是在
6.5 错排问题
把
n 个编号为1 - n 的物体放入n 个编号为1 - n 的盒子,每个盒子里放一个物体,求有几种方案使得所有球和所在的盒子的编号都不相同,即n 的错排数。
我们设
我们假设物体
- 若物体
b 放入盒子A 中:
那么剩下的
- 若物体
b 没有放入盒子A 中:
因为物体
所以物体
边界:
6.6 整数划分
(下列的整数都默认是正整数)
1.
n 划分成若干个整数之和的方案数。
可以用完全背包解决,有
f[1] = 1;//边界
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
f[j] += f[j - i];
}
}
2.
n 划分成若干个不同整数之和的方案数。