首先说明逆元的概念,类似于倒数的性质。
方程ax≡1(mod p),的解称为a关于模p的逆,当gcd(a,p)==1(即a,p互质)时,方程有唯一解,否则无解。
对于一些题目会要求把结果MOD一个数,通常是一个较大的质数,对于加减乘法通过同余定理可以直接拆开计算,
但对于(a/b)%MOD这个式子,是不可以写成(a%MOD/b%MOD)%MOD的,但是可以写为(a*b-1)%MOD,其中b-1表示b的逆元。
知道了逆元的作用,接下来就是逆元的求法。
首先,有一个费马小定理:
费马小定理(Fermat’s little theorem)是数论中的一个重要定理,在1636年提出,其内容为: 假如p是质数,且gcd(a,p)=1,那么 a(p-1)≡1(mod p),即:假如a是整数,p是质数,且a,p互质(即两者只有一个公约数1),那么a的(p-1)次方除以p的余数恒等于1。
意思很明了,由上方的公式很容易推导出:a*a(p-2)≡1(mod p)对于整数a,p,a关于p的逆元就是a^(p-2),直接快速幂解之即可,但注意这个定理要求a,p互质!
逆元 - 组合数取模
现在目标是求Cmn%pCnm%p,p为素数(经典p=1e9+7)
虽然有Cmn=n!m!(n−m)!Cnm=n!m!(n−m)!,但由于取模的性质对于除法不适用,所以Cmn%pCnm%p≠(n!%pm!%p∗(n−m)!%p)%p(n!%pm!%p∗(n−m)!%p)%p
所以需要把“除法”转换成“乘法”,才能借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!
逆元:对于a和p(a和p互素),若ab%p≡1,则称b为a%p的逆元。
那这个逆元有什么用呢?试想一下求(ab)(ab)%p,如果你知道b%p的逆元是c,那么就可以转变成(ab)(ab)%p = ac%p = (a%p)(c%p)%p
那怎么求逆元呢?这时候就要引入强大的费马小定理!
费马小定理:对于a和素数p,满足
a
p
−
1
a^{p-1}
ap−1%p≡1
接着因为ap−1ap−1 = ap−2∗aap−2∗a,所以有ap−2∗aap−2∗a%p≡1!对比逆元的定义可得,ap−2ap−2是a的逆元!
所以问题就转换成求解ap−2ap−2,即变成求快速幂的问题了(当然这需要满足p为素数)。
现在总结一下求解Cmn%pCnm%p的步骤:
通过循环,预先算好所有小于max_number的阶乘(%p)的结果,存到fac[max_number]里 (fac[i] = i!%p)
求m!%p的逆元(即求fac[m]的逆元):根据费马小定理,x%p的逆元为xp−2xp−2,因此通过快速幂,求解fac[m]p−2fac[m]p−2%p,记为M
求(n-m)!%p的逆元:同理为求解fac[n−m]p−2fac[n−m]p−2%p,记为NM
Cmn%pCnm%p = ((fac[n]M)%pNM)%p
//快速幂求x^n%mod
long long pow_mod(long long x, long long n, long long mod) {
long long res = 1;
while (n) {
if (n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
long long fac[MAX_NUMBER+5];
long long n, m, p;
int main() {
while (~scanf("%lld %lld %lld", &n, &m, &p)) {
//预处理求fac,fac[i] = i!%p
fac[0] = 1;
for (int i = 1; i <= n; i++) {fac[i] = fac[i - 1] * i % p;}
//组合数 = n!*(m!%p的逆元)*((n-m)!%p的逆元)%p
printf("%lld\n", fac[n] * pow_mod(fac[m], p - 2, p) % p * pow_mod(fac[n - m], p - 2, p) % p);
}
}