数学知识 --- 组合数

什么是组合数?

  • 组合数公式是指从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
",fact[a] * infact[b] % mod * infact[a - b] % mod);
	}    
    return 0;
}
原文地址:https://www.cnblogs.com/bingers/p/14088836.html