lucas定理

今天遇到一道组合数取模的题,毫无疑问爆零了,我出题人真是个sb,连Lucas定理都不知道QAQ

Lucas定理: 求解C(n,m)%p , n 和 m 是 非负整数 , p是素数

结论1:Lucas(n,m,p) = C(n%p,m%p)* Lucas(n/p,m/p,p)

结论2. 把n写成p进制a[n]a[n-1]a[n-2]...a[0],把m写成p进制b[n]b[n-1]b[n-2]...b[0],则C(n,m)与C(a[n],b[n])*C(a[n-1],b[n-1])*C(a[n-2],b[-2])*....*C(a[0],b[0])模p同余。

*注:n,m不能大于10^5,不大于情况下用逆元的方法可以解决,如果大了就不能解决。

/*
 *  Created on: 2017-06-02
 *      Author: zjq
 *      求C(n,m)%p, p为素数
 *      Lucas定理: Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p);
 */
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
LL fac[20005];
void init(LL p)
{
    fac[0] =1;
    for(LL i =1; i <= p; i++)
        fac[i] = fac[i-1]*i % p;
}
LL exp_mod(LL a, LL b, LL p)
{
    LL tmp = a % p, ans =1;
    while(b)
    {
        if(b & 1)  ans = ans * tmp % p;
        tmp = tmp*tmp % p;
        b >>=1;
    }
    return  ans;
}
LL C(LL n, LL m, LL p)
{
    if(m > n)
    	return 0;
    return  fac[n]*exp_mod(fac[m]*fac[n-m], p-2, p) % p;//逆元
}
LL Lucas(LL n, LL m, LL p)
{
    if(m ==0)
    	return 1;
    return  (C(n%p, m%p, p)*Lucas(n/p, m/p, p))%p;
}
int main()
{
	freopen("comb.in","r",stdin);
	freopen("comb.out","w",stdout);
	LL n,m,p;
	scanf("%lld %lld %lld",&n,&m,&p);
	init(p);
	printf("%lld\n",Lucas(n,m,p));
    return 0;
}

另一种

现在目标是求 Cmn%p Cnm%p,p为素数(经典p=1e9+7)

虽然有 Cmn=n!m!(nm)! Cnm=n!m!(n−m)!,但由于取模的性质对于除法不适用,所以 Cmn%p Cnm%p (n!%pm!%p(nm)!%p)%p (n!%pm!%p∗(n−m)!%p)%p

所以需要把“除法”转换成“乘法”,才能借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!

  逆元:对于a和p,若a*b%p≡1,则称b为a%p的逆元。

那这个逆元有什么用呢?试想一下求 (ab) (ab)%p,如果你知道b%p的逆元是c,那么就可以转变成 (ab) (ab)%p = a*c%p = (a%p)(c%p)%p

那怎么求逆元呢?这时候就要引入强大的费马小定理!

  费马小定理:对于a和素数p,满足$a^{p-1}$%p≡1

接着因为 ap1 ap−1 =  ap2a ap−2∗a,所以有 ap2a ap−2∗a%p≡1!对比逆元的定义可得, ap2 ap−2是a的逆元!

所以问题就转换成求解 ap2 ap−2,即变成求快速幂的问题了(当然这需要满足p为素数)。

现在总结一下求解 Cmn%p Cnm%p的步骤:

  1. 通过循环,预先算好所有小于max_number的阶乘(%p)的结果,存到fac[max_number]里 (fac[i] = i!%p)
  2. 求m!%p的逆元(即求fac[m]的逆元):根据费马小定理,x%p的逆元为 xp2 xp−2,因此通过快速幂,求解 fac[m]p2 fac[m]p−2%p,记为M
  3. 求(n-m)!%p的逆元:同理为求解 fac[nm]p2 fac[n−m]p−2%p,记为NM
  4. Cmn%p Cnm%p = ((fac[n]*M)%p*NM)%p

#include <iostream>  
#include <cstdio>  
#include <cstdlib>  
#include <cstring>  
#include <algorithm>  
#include <cmath>  
using namespace std;  
typedef long long ll;  
typedef long double ld;  
const ld eps=1e-10;  
const int inf = 0x3f3f3f;  
const int maxn = 100005;  
ll fac[maxn];  
  
ll pow_mod(ll a,ll b,ll mod)  
{  
    ll ret = 1;  
    while(b)  
    {  
        if(b & 1) ret = (ret*a)%mod;  
        a = (a*a)%mod;  
        b >>= 1;  
    }  
    return ret;  
}  
  
ll Fac(ll p)  
{  
    fac[0] = 1;  
    for(int i = 1;i <= p;i++)  
    {  
        fac[i] = (fac[i-1] * i)%p;  
    }  
}  
  
ll lucas(ll n,ll m,ll p)  
{  
    ll ret = 1;  
    while(n && m)  
    {  
        ll a = n % p;  
        ll b = m % p;  
        if(a < b)  
            return 0;  
        ret = (ret*fac[a]*pow_mod(fac[b]*fac[a-b]%p,p-2,p))%p;        
        n /= p;  
        m /= p;  
    }  
    return ret%p;  
}  
  
  
int main()  
{  
    int t;  
    scanf("%d",&t);  
    while(t--)  
    {  
        ll a,b,p;  
        scanf("%I64d%I64d%I64d",&a,&b,&p);  
        Fac(p);  
        ll ans = lucas(a+b,b,p);  
        printf("%I64d\n",ans);  
    }  
} 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值