本文已参与「新人创作礼」活动,一起开启掘金创作之路
简单优化(没有模运算)
当m < n-m 时,把n - m 改为 m,使用下方算法,简单优化
代码如下:
#include<cstdio>
using namespace std;
long long C(int n, int m){
if (m<n-m) m=n-m;
long long ans=1;
for (int i=m+1;i<=n;i++) ans *= i;
for (int i=1;i<=n-m;i++) ans /= i;
return ans;
}
int main(){
printf("%lld\n",C(n,m));
return 0;
}
进阶优化(逆元 - 组合数取模)
问题:求%p(经典p=1e9+7)
在运算中,取模性质对除数不适用,需要将“除法”转换为“乘法”,才能借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!
逆元:对于a和p(a和p互素),若a*b%p≡1,则称b为a%p的逆元。
那这个逆元有什么用呢?试想一下求( )%p,如果你知道b%p的逆元是c,那么就可以转变成( )%p = a*c%p = (a%p)(c%p)%p
那怎么求逆元呢?这时候就要引入强大的费马小定理!
费马小定理:对于a和素数p,满足 %p≡1
接着因为 = ∗a,所以有∗a%p≡1!对比逆元的定义可得,是a的逆元!
所以问题就转换成求解,即变成求快速幂的问题了(当然这需要满足p为素数)。
快速幂请参考博主的另一篇文章:快速幂算法
现在总结一下求解%p的步骤:
- 通过循环,预先算好所有小于max_number的阶乘( %p )的结果,存到fac[max_number]里 ( fac[i] = i ! %p )
- 求m!%p的逆元( 即求fac[m]的逆元 ):根据费马小定理,x%p的逆元为,因此通过快速幂,求解 %p,记为M
- 求(n-m)!%p的逆元:同理为求解%p,记为NM
- %p = ( ( fac[n] * M)%p * NM)%p
用C++实现的代码,输入为n,m,p,要求0<=m<=n<=1e6(否则fac存不下),且gcd(p,n!)=1(即互素),输出为Cmn%p
long long fac[MAXN];
long long n,m,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;
}
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);
}
}
参考链接——逆元 - 组合数取模