思路:
求组合数有两种算法,根据预处理的数据量大小来判断用哪个。
两者的思想都是先进行预处理出一个表,每次询问都通查找表中处理好的数据来获得答案。
当预处理数据量较小时:
主要内容 | 数据类型 | 作用 |
二维数组c[N][N] | int | c[a][b]表示在a里选b个数的方案个数 |
思路:
采用递推的方法:c[a][b]=c[a-1][b]+c[a-1][b];
解释:
有点类似于DP:假设有a个苹果,要从中选出b个苹果。假设a中最后一个苹果为k,在所有选法中,只有选k的和不选 k的两种情况,分别对应c[a-1][b-1]与c[a-1][b]两种情况,也就有c[a][b]=c[a-1][b-1]+c[a-1][b];
模板题:
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 C(b,a)mod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤2000
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码:
#include <iostream>
using namespace std;
const int N=2010,mod=1e9+7;
int c[N][N];
void init(){
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){//因为j初始为0所以i小于0的时候就不会发生进入循环,也就不用担心i-1越界
if(!j) c[i][j]=1;
else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
}
int main(){
init();
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",c[a][b]);
}
return 0;
}
当预处理数据量较大时:
思路:
主要内容 | 数据类型 | 作用 |
fact[] | int | fact[i]储存i的阶乘 |
infact[] | int | infact[i]储存i的阶乘的逆元 |
设C(b,a)为在a个数里选b个数的方案数,现要求C(b,a).(如果像方法1一样处理,会超时)
方法2:
C(b,a)=a!/((a-b)!*b!);
求一遍数的阶乘,然后带入上述公式得到答案;
这里有一个问题:就是我们在预处理fact[]时会进行mod处理防止数据过大(因为是阶乘会很大),
但是(n/m)mod k !=(n mod k)/(m mod k);
所以直接将阶乘带入公式会导致答案出错。
根据a/b同余于a*(b的逆元),所以公式a!/((a-b)!*b!)==a!*((a-b)!)的逆元*(b!)的逆元(逆元在快速幂里讲过).
所以要再预处理一个逆元数组infact[];
模板题:
给定 n 组询问,每组询问给定两个整数 a,b,请你输出 Cbamod(109+7) 的值。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一组 a 和 b。
输出格式
共 n 行,每行输出一个询问的解。
数据范围
1≤n≤10000,
1≤b≤a≤105
输入样例:
3
3 1
5 3
2 2
输出样例:
3
10
1
代码:
//求组合数的两种方法都是进行预处理,不在询问的基础上拓深时间复杂度
#include <iostream>
using namespace std;
typedef long long LL;
const int N=1e5+10,mod=1e9+7;//mod为质数
LL fact[N],infact[N];//fact表示阶乘,infact表示阶乘的逆元
int qmi(int a,int b,int m){//快速幂,时间复杂度log级
LL res=1;
while(b){
if(b&1) res=res*a%m;
a=(LL)a*a%m;
b>>=1;
}
return res;
}
int main(){
//预处理
fact[0]=infact[0]=1;
for(int i=1;i<N;i++){
fact[i]=fact[i-1]*i%mod;//暂时改变数据类型
}
int n;
scanf("%d",&n);
while(n--){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",fact[a]/(fact[a-b]*fact[b]));//三个1e9相乘会爆LL所以要及时模,模两个
}
return 0;
}