题目描述:
n <= 10000;
情况一:1 <= a,b <= 3000;
该数据范围不大,我们可以采用递推的方式求解组合数!时间复杂度为 O(n2) = 1e6级别左右!
组合数的本质是:从n个不同的数中选出m个的方案数是:C(n, m);
对于第一个数有选或者不选两种决策:
- 不选第一个数,则从剩下的n-1个数中选m个数的方案数:C(n-1,m);
- 选第一个数,则从剩下的n-1个数中选m-1个数的方案数:C(n-1,m-1);
- 所以递推式:C(n,m) = C(n-1,m) + C(n-1,m-1);
- 组合数的重要性质如下:
1.C(n,0) = C(n,n) = 1;
2.C(n, m) = C(n, n-m);
3.C() = 递推式,,,,上面那个!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7, N = 2e3 + 10;
int t;
int C[N][N];
void init()
{
for (int i=0; i < N; i ++)
{
for (int j=0; j <= i; j ++)
{
if (j == 0) C[i][j] = 1;
else C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
}
}
}
int main()
{
cin >> t;
init();
while (t --)
{
int n, m;
cin >> n >> m;
cout << C[n][m] << endl;
}
return 0;
}
情况二:1≤b≤a≤105
- 首先递推式会tle,那么采用公式法去直接计算!
- 但是不难发现阶乘存在过大的情况,分子分母均是会爆int的,一方面要记得开 longlong,另一方面就是除法的处理,除法并不好处理,而相对于来说:乘法更好处理。那么如何进行转化呢?这里就需要考虑到我们的除法转乘法了!
- 乘法的逆元,也就是说,一个数a,模一个数p,但是余数不等于1,所以需要找一个数x,使得a*x % p == 1,则这个数x就是a的逆元,再到公式当中去,想找出逆元即找的是 1/(n-m)! 和 1/m!的逆元的值!
- 由于p是质数 = 1e9 + 7;且n 和 m 都小于 p,所以n,m都和p互质! (举例:p=17, n=16 =》互质,因为质数的因子只有1和它本身,而互质的话,即 gcd (p, n) = 1)
- 所以根据两个数(a, p)互质,想到费马定理:ap-2 % p = 1;(n-m)!的逆元就是:[(n-m)!]p-2;
- 开两个数组:
f[x] 存 x!%p的值!
g[x] 存(x!)-1% p的值!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, mod = 1e9 + 7;
typedef long long LL;
int f[N], g[N];
int quick_power(int a, int b, int p)
{
int res=1;
while (b)
{
if (b&1) res = (LL)res * a % p;
a = (LL)a*a % p;
b >>= 1;
}
return res%p;
}
void init()
{
f[0] = g[0] = 1;
for (int i=1; i < N; i ++)
{
f[i] = (LL)f[i-1] * i % mod;
g[i] = (LL)g[i-1] * quick_power(i, mod-2, mod) % mod;
}
}
int main()
{
int n;
cin >> n;
init();
while (n -- )
{
int a, b;
cin >> a >> b;
cout << (LL)f[a] * g[b] % mod * g[a-b] % mod << endl;
}
return 0;
}