前言:组合数在高中大家应该都学过,也是一个重要的数学知识,希望大家能够完全掌握
一、组合数基本知识
定义:
组合是数学的重要概念之一。从 n 个不同元素中每次取出 m 个不同元素 ,不管其顺序合成一组,称为从 n 个元素中不重复地选取 m 个元素的一个组合。所有这样的组合的种数称为组合数。
计算公式:
在线性写法中被写作C(n,m)。
组合数的计算公式为
n 元集合 A 中不重复地抽取 m 个元素作成的一个组合实质上是 A 的一个 m 元子集合。如果给集 A 编序 成为一个序集,那么 A 中抽取 m 个元素的一个组合对应于数段 到序集 A 的一个确定的严格保序映射。组合数 的常用符号还有
实际上是自然数集上的二元运算,这种运算既不满足交换律也不满足结合律。
性质:
互补性质
组合数基本公式
即从n个不同元素中取出m个元素的组合数=从n个不同元素中取出 (n-m) 个元素的组合数;
这个性质很容易理解,例如C(9,2)=C(9,7),即从9个元素里选择2个元素的方法与从9个元素里选择7个元素的方法是相等的。
规定:C(n,0)=1 C(n,n)=1 C(0,0)=1
2.组合恒等式
若表示在 n 个物品中选取 m 个物品,则如存在下述公式:C(n,m)=C(n,n-m)=C(n-1,m-1)+C(n-1,m)。
二、例题
1.组合数I
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2010, mod = 1e9 + 7;
int C[N][N], T;
void init() {
for (int i = 0; i < N; ++ i) {
for (int j = 0; j <= i; ++ j) {
if (j == 0 || j == i) C[i][j] = 1;
else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
}
}
}
int main()
{
init();
scanf("%d", &T);
while (T -- ) {
int n, m;
scanf("%d%d", &n, &m);
printf("%d\n", C[n][m]);
}
return 0;
}
2.组合数II
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10, P = 1e9 + 7;
using LL = long long ;
int fact[N], infact[N];
int T;
int qmi(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (LL)ans * a % P;
a = (LL)a * a % P;
b >>= 1;
}
return ans;
}
void init() {
fact[0] = infact[0] = 1;
for (int i = 1; i < N; ++ i) {
fact[i] = (LL)fact[i - 1] * i % P;
infact[i] = (LL)infact[i - 1] * qmi(i, P - 2) % P;
}
}
int main()
{
init();
scanf("%d", &T);
while (T -- ) {
int n, m;
scanf("%d%d", &n, &m);
printf("%d\n", (LL)fact[n] * infact[m] % P * infact[n - m] % P);
}
return 0;
}
3、组合数III
AC代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long ;
int T, P;
int qmi(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (LL)ans * a % P;
a = (LL)a * a % P;
b >>= 1;
}
return ans;
}
// 递推求解朴素的C(a,b)
int C(int n, int m) {
if (m > n) return 0;
int ans = 1;
// i控制的是分母的循环,对于b!,从1到b总共有b个数
// j控制的是分子的循环,从a到a-b+1总共有b个数
// 因此可以发现,分子和分母循环的次数是相同的
for (int i = 1, j = n; i <= m; ++ i, -- j) {
ans = (LL)ans * j % P;
ans = (LL)ans * qmi(i, P - 2) % P;
}
return ans;
}
// 用Lucas定理递归求解
int Lucas(LL n, LL m) {
if (n < P && m < P ) return C(n, m);
return (LL)C(n % P, m % P) * Lucas(n / P, m / P) % P;
}
int main()
{
scanf("%d", &T);
while (T -- ) {
LL n, m;
scanf("%lld%lld%d", &n, &m, &P);
printf("%d\n", Lucas(n, m));
}
return 0;
}
4、组合数IV
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int s[N], primes[N], cnt;
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; ++ i) {
if (!st[i] ) primes[cnt ++ ] = i;
for (int j = 0; j < cnt && primes[j] <= n / i; ++ j) {
st[i * primes[j]] = true;
if (i % primes[j] == 0 ) break;
}
}
}
int get(int n, int p) {
int ans = 0;
while (n) {
ans += n / p;
n /= p;
}
return ans;
}
vector<int> mul(vector<int> &A, int b) {
vector<int> C;
for (int i = 0, t = 0; i < A.size() || t; ++ i) {
if (i < A.size()) t += A[i] * b;
C.push_back(t % 10);
t /= 10;
}
while (C.size() > 1 && C.back() == 0) C.pop_back();
return C;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
get_primes(n);
for (int i = 0; i < cnt; ++ i) {
int p = primes[i];
s[i] = get(n, p) - get(m, p) - get(n - m, p);
}
vector<int> ans;
ans.push_back(1);
for (int i = 0; i < cnt; ++ i)
for (int j = 0; j < s[i]; ++ j)
ans = mul(ans, primes[i]);
for (int i = ans.size() - 1; i >= 0; -- i)
printf("%d", ans[i]);
puts("");
return 0;
}
5、满足条件的01序列
AC代码:
// 写法1
#include <bits/stdc++.h>
using namespace std;
const int P = 1e9 + 7;
using LL = long long ;
int n;
int qmi(int a, int b) {
int ans = 1;
while (b) {
if (b & 1) ans = (LL)ans * a % P;
a = (LL)a * a % P;
b >>= 1;
}
return ans;
}
int main() {
scanf("%d", &n);
int a = 2 * n, b = n, ans = 1;
for (int i = 1, j = a; i <= n; ++ i, -- j) {
ans = (LL)ans * j % P;
ans = (LL)ans * qmi(i, P - 2) % P;
}
ans = (LL)ans * qmi(n + 1, P - 2) % P;
printf("%lld\n", ans);
return 0;
}