数学知识 --- 组合数
什么是组合数?
- 组合数公式是指从n个不同元素中,任取m(m≤n)个元素并成一组,叫做从n个不同元素中取出m个元素的一个组合;
- 从n个不同元素中取出m(m≤n)个元素的所有组合的个数,叫做n个不同元素中取出m个元素的组合数。用符号c(n,m) 表示
前置知识:排列公式
- 排列公式是建立一个模型,从n个不相同元素中取出m个排成一列(有序)
- 第一个位置可以有n个选择,第二个位置可以有n-1个选择(已经有1个放在前一个位置),则同理可知第三个位置可以有n-2个选择,以此类推第m个位置可以有n-m+1个选择
组合数公式
- 组合公式的推导是由排列公式去掉重复的部分而来的
- 组合公式对应另一个模型,取出m个成为一组(无序)
- 排列公式建立有序,而组合公式建立为无序,有序情况当然比无序多
通过递推来求组合数,时间复杂度为O(N^2)
c(n,m)=c(n-1,m-1)+c(n-1,m)
- 从n中选m个元素可以选取某元素的包含与不包含,分成两类情况,即m个被选择元素包含了这个元素和m个被选择元素不包含该元素。
- 前者相当于从n-1个元素中选出m-1个元素的组合,即c(n-1,m-1)
- 后者相当于从n-1个元素中选出m个元素的组合,即c(n-1,m)
代码如下:
- 预处理所有的情况,a,b范围在[1,2000]
#include<iostream>
using namespace std;
const int N = 2e3 + 10,mod = 1e9 + 7;
int n;
int a,b;
int c[N][N];
void init(){
for(int i = 0; i < N; i ++ ){
for(int j = 0; j <= i; j ++ ){
if(!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
}
int main(){
init();
cin >> n;
while(n -- ){
cin >> a >> b;
cout<<c[a][b]<<endl;
}
return 0;
}
通过组合数公式求组合数,时间复杂度为O(nlogn)
- 用fact[]数组来存放(n!)mod(1e9 + 7)
- 用infact[]数组来存放(n!)的逆元mod(1e9 + 7)
- 求逆元用快速幂和费马小定理
- 组合数为:fact[a] * infact[b - a] * infact[b]
- 防止超出范围:fact[a] * infact[b] % mod * infact[a - b] % mod
代码如下:
- 预处理,a,b范围在[1,1e5]
#include<iostream>
using namespace std;
#define ll long long
const int N = 1e5 + 10,mod = 1e9 + 7;
int n;
ll fact[N],infact[N];
ll qmi(ll a,ll b,ll p){
ll res = 1%p;
while(b){
if(b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
void init(){
fact[0] = infact[0] = 1;
for(int i = 1; i < N; i ++ ){
fact[i] = fact[i - 1] * i % mod;
infact[i] = infact[i - 1] * qmi(i,mod - 2,mod) % mod;
}
}
int main(){
cin >> n;
while(n -- ){
int a,b;
cin >> a >> b;
printf("%lld\n",fact[a] * infact[b] % mod * infact[a - b] % mod);
}
return 0;
}