[算法 18_001] Lucas 定理与大组合数取余

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/buildcourage/article/details/79905550

Lucas 定理

该定理是用来求当 ( n m ) 中的 m , n 很大而 p 为素数时, ( n m ) ( mod p ) 的值。
Lucas 定理:令 n = s p + q , m = t p + r . ( q , r < p )
那么:

( n m ) ( s p + q t p + r ) ( s t ) ( q r ) ( mod p )

则在编程时,只需继续对 ( s t ) 应用 Lucas 定理即可。代码可以递归地完成这个过程,终止条件是 t = 0 。时间复杂度为 O ( log p n p )

大组合数取余

应用 Lucas 定理,可以将求 ( n m ) ( mod p ) 的问题转化为求 ( q r ) ( s t ) ( mod p ) 的问题,因为 q , r < p 可对 ( q r ) ( mod p ) 直接求组合数,而对 ( s t ) ( mod p ) 递归使用 Lucas 定理求值。
而对于小组合数有:

( q r ) q ! r ! ( q r ) ! ( mod p )

对于阶乘很大的情况,计算机编程时可能需要对分子分母分别取余,但是对于除法不能轻易使用同余定理(只在 + , , 三种运算下有效),所以希望可以将除法取余转换为等价的乘法取余,这时便需要另外重要的知识:逆元以及费马小定理。

逆元

逆元定义:对于正整数 a p ,如果有 a x 1 ( mod p ) ,那么把这个同余方程中 x 的最小正整数解叫做 a ( mod p ) 的逆元。

费马小定理

定义:假如 p 是质数,且 gcd ( a , p ) = 1 ,即 a , p 互质,那么 a p 1 1 ( mod p )

由费马小定理可得:

a p 1 1 ( mod p ) a p 2 1 a ( mod p )

因此当 a , p 互质且 p 为素数时,有:
b a b a p 2 ( mod p )

带入组合数公式,即有:
( q r ) q ! r ! ( q r ) ! ( q ! ) ( r ! ( q r ) ) p 2 ( mod p )

这样便将除数求余转化为同余的乘法求余运算。
这里出现了求幂运算 a p 2 ,可以使用快速求幂算法

快速求幂

//cpp
LL quickPow(LL n,LL m){
    LL ans = 1;
    n %= mod;
    while(m){
        if(m & 1)
            ans = ans * n % mod;
        n = n * n % mod;
        m >>= 1;
    }
    return ans;
}

对于求阶乘,可以使用一个全局数组缓存结果,这样就不用每次求了

阶乘

//cpp
void getFac(int n){
    fac[0]=fac[1]=1;
    for(int i=2;i<=n;++i){
        fac[i]=fac[i-1] * i % mod;
    }
}

下面给出求组合数和Lucas定理的代码实现

求组合数

//cpp
LL C(LL n,LL m){
    if(m>n)
        return 0;
    return fac[n]*quickPow(fac[m]*fac[n-m],mod-2) % mod;
}

Lucas 定理

//cpp
LL Lucas(LL n,LL m){
    if(m == 0)
        return 1;
    return C(n%mod,m%mod)*Lucas(n/mod,m/mod) % mod;
}

参考资料

[1] https://blog.csdn.net/wyg1997/article/details/52152282 “Lucas定理 & 逆元学习小结”
[2] https://baike.baidu.com/item/%E8%B4%B9%E9%A9%AC%E5%B0%8F%E5%AE%9A%E7%90%86 “费马小定理”
[3] https://baike.baidu.com/item/lucas/4326261?fr=aladdin “Lucas 定理”

猜你喜欢

转载自blog.csdn.net/buildcourage/article/details/79905550