问题:求组合数C(n, m)(n在下,m再上)
先了解一下两个小知识
费马小引理
这个可以用来求特殊情况下的逆元。
逆元
定义:如果 a * x = 1(mod p),且gcd(a, p) = 1(等价于a,p互质),那 x 就为模 p 意义下 a 的乘法逆元。
根据上面的费马小引理,我们可以的得知假如 p 是素数,那a^(p-1) = 1(mod p).成立
那a^(p-2) 就是模 p 意义下 a 的逆元(只适用于 p 是素数,下次补篇求逆元的博客)
用逆元求 除法取模
除法不能直接取模
小定理:a/b % p = a*x % mod p= 1 % p;(x为模p意义下a的逆元)
证明:a/b * b * x = a * x (mod p) ( b * x = 1(mod p) );
这样就解决了求组合数之间的除法了。
分段讲代码:
快速幂模板
ll Power(ll x, ll y) {
ll n = y, ans = 1, res = x;
while(n){
if(n & 1) ans = ans * res % mod;
n >>= 1;
res = res * res % mod;
}
return ans%mod;
}
初始化:求各个阶乘(mod p),和各个阶乘在模p意义下的逆元。
for(int i=1; i<=N; i++) C[i] = (C[i-1]*i) % mod;
inv[N-1] = Power(C[N-1], mod-2); //费马小引理求逆元。
for(int i=N-2; i>=0; i--) {
inv[i] = inv[i+1]*(i+1) % mod; //阶乘逆元递推关系。
}
解释一下阶乘的递推公式 ( k ! ) − 1 (k!)^{-1} (k!)−1表示 k k k的阶乘的逆
( k ! ) − 1 = ( ( k − 1 ) ! ∗ k ) − 1 = ( k − 1 ) − 1 ∗ k − 1 (k!)^{-1}=((k-1)!*k)^{-1}=(k-1)^{-1}*k^{-1} (k!)−1=((k−1)!∗k)−1=(k−1)−1∗k−1
所以: ( k ! ) − 1 ∗ k = ( ( k − 1 ) ! ) − 1 (k!)^{-1}*k = ((k-1)!)^{-1} (k!)−1∗k=((k−1)!)−1
最终求解组合数
ll U(ll x, ll y) {
if(y > x || x < 0 || y < 0) return 0;
if(y == 0 || x == y) return 1;
//A(x,x) / (A(y,y) * A(x-y,x-y),转化成逆元相乘
return (C[x] * inv[y]%mod * inv[x-y]) % mod;
}
总代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const ll N = 2e6+5;
ll C[N] = {
1};
ll inv[N];
ll Power(ll x, ll y) {
ll n = y, ans = 1, res = x;
while(n){
if(n & 1) ans = ans * res % mod;
n >>= 1;
res = res * res % mod;
}
return ans%mod;
}
ll U(ll x, ll y) {
if(y > x || x < 0 || y < 0) return 0;
if(y == 0 || x == y) return 1;
return (C[x] * inv[y]%mod * inv[x-y]) % mod;
}
int main() {
for(int i=1; i<=N; i++) C[i] = (C[i-1]*i) % mod;
inv[N-1] = Power(C[N-1], mod-2);
for(int i=N-2; i>=0; i--) {
inv[i] = inv[i+1]*(i+1) % mod;
}
ll t, a, b, temp;
scanf("%lld", &t);
while(t--) {
scanf("%lld%lld", &a, &b);
a--, b--;
a += b;
printf("%lld\n", U(a, b));
}
return 0;
}