从拓展卢卡斯定理到卢卡斯定理

本文深入探讨了拓展卢卡斯定理和卢卡斯定理的数学原理及其应用,通过详细解释如何利用这两种定理解决组合数学中的模运算问题,为读者提供了算法实现的代码示例。

从拓展卢卡斯定理到卢卡斯定理

1. 拓展卢卡斯定理
  1. 拓展卢卡斯定理是在modmodmod不是质数但是将modmodmod质因数分解后得到 p1k1p2k2⋯pnknp_1^{k_1}p_2^{k_2}\cdots p_n^{k_n}p1k1p2k2pnkn 之后,pikip_i^{k_i}piki都很小的情况下
    解决Cnm%modC_n^m\%modCnm%mod的一种方法。

  2. 由于各个piki都互质,我们可以单独考虑每一个pikip_i^{k_i}都互质,我们可以单独考虑每一个p_i^{k_i}pikipiki,最后再用中国剩余定理合并。

  3. 考虑到Cnm=n!m!(n−m)!C_n^m=\frac {n!} {m!(n-m)!}Cnm=m!(nm)!n!,我们可以分别求各个的阶乘的答案,再合并起来求CnmC_n^mCnm

  4. 算法过程

    1. 假设当前考虑的是pkp^kpk

    2. 由于阶乘中有很多数有p这个因子,那么我们得先把p这个因子提出来

    3. 拿下面的数列做例子,当前考虑的是3^2,我们把3的倍数筛出来

      1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗191*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*1912345678910111213141516171819

      变成了
      KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲&1 * 2 * 4 * 5 …

    4. 显然可以发现,在同一层每pk每p^kpk个构成了一个循环,且每乘333的倍数,内部的也是和第一层类似且循环的。

    5. 统计是ppp的倍数的p的幂次方之和

      1. 对于n!n!n! 以内是ppp的倍数的p的幂次方之和,n/pn/pn/p可以得到至少是1次幂之和,n/p2n/{p^2}n/p2 $ {n/p^3}$ 以此类推即可
    6. 统计不是p的倍数的乘积

      1. 每一层统计循环的乘积,统计循环了多少次,再统计单独的即可。
  5. 代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define maxn 100005
    using namespace std;
    typedef long long ll;
    ll p,n,m,tot;
    ll quickpow(ll x,ll k,const ll mod)
    {
        ll ans=1;
        while(k)
        {
            if(k&1)
                ans=(ans*x)%mod;
            x=(x*x)%mod;
            k>>=1;
        }
        return ans;
    }
    void exgcd(ll a,ll b,ll &x,ll &y)
    {
        if(!b)
            x=1,y=0;
        else
            exgcd(b,a%b,y,x),y-=a/b*x;
    }
    ll getinv(ll a,ll mod)
    {
        if(!a) return 0;
        ll x,y;
        exgcd(a,mod,x,y);
        x=(x%mod+mod)%mod;
        return !x ? mod : x;
    }
    ll pri[maxn],mi[maxn];
    void fenjie(ll now)
    {
        ll temp=now;
        for(ll i=2;i*i<=now;i++)
        {
            if(temp%i==0)
            {
                pri[++tot]=i; mi[tot]=1;
                while(temp%i==0)
                    mi[tot]=(mi[tot]*i)%p,temp/=i;
            }
        }
        if(temp!=1)
            pri[++tot]=temp,mi[tot]=temp;
    }
    ll cal(ll now,ll p1,ll mod)
    {
        if(!now) return 1;
        ll ans=1;
        if(now/mod)
        {
            for(int i=2;i<=mod;i++)//这里可以改成前缀和的形式
                if(i%p1)
                    ans=(ans*i)%mod;
            ans=quickpow(ans,now/mod,mod);
        }
        for(int i=2;i<=now%mod;i++)//这里可以改成前缀和的形式 
            if(i%p1)
                ans=(ans*i)%mod;
        return (ans*mul(now/p1,p1,mod))%mod;
    }
    ll c(ll n,ll m,ll p1,ll mod)
    {
        if(m>n) return 0;
        ll a=cal(n,p1,mod),b=cal(m,p1,mod),c=cal(n-m,p1,mod);
        ll k=0;
        for(ll i=n;i;i/=p1) k+=i/p1;
        for(ll i=m;i;i/=p1) k-=i/p1;
        for(ll i=(n-m);i;i/=p1) k-=i/p1;
        return a*getinv(b,mod)%mod*getinv(c,mod)%mod*quickpow(p1,k,mod)%mod;
    }
    ll china(ll n,ll m)
    {
        ll ans=0;
        for(int i=1;i<=tot;i++)
        {
            ll res=c(n,m,pri[i],mi[i]);
            res=(res*(p/mi[i])%p);
            res=(res*getinv(p/mi[i],mi[i]))%p;
            ans=(ans+res)%p;
        }
        return ans;
    }
    int main()
    {
        scanf("%lld%lld%lld",&n,&m,&p);
        fenjie(p);
        ll ans=china(n,m);
        return printf("%lld\n",ans),0;
    }
    
2. 卢卡斯定理
  1. 为什么先拓展再本身呢,因为卢卡斯定理和拓展卢卡斯好像没什么肉眼可见的关系

  2. 做法很简单,就每次将递归后的值乘上对当前n,m取模之后求组合数的值。

  3. 代码

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    #include<algorithm>
    #define maxn 200005
    using namespace std;
    typedef long long ll;
    ll n,m,p;
    ll quickpow(ll pp,ll k)
    {
    	ll ans=1;
    	while(k)
    	{
    		if(k&1)
    			ans=(ans*pp)%p;
    		pp=(pp*pp)%p;
    		k>>=1;
    	}
    	return ans;
    }
    ll fac[maxn],inv[maxn];
    ll c(ll n1,ll m1)
    {
    	if(n1<m1) return 0;
    	return fac[n1]*inv[m1]%p*inv[n1-m1]%p;
    }
    ll lucas(ll n1,ll m1)
    {
    	if(n1<m1) return 0;
    	if(!n1) return 1;
    	return c(n1%p,m1%p)*lucas(n1/p,m1/p)%p;
    }
    int main()
    {
    	int _;
    	scanf("%d",&_);
    	while(_--)
    	{
    		scanf("%lld%lld%lld",&n,&m,&p);
    		fac[0]=1;
    		for(int i=1;i<=p-1;i++)
    			fac[i]=(fac[i-1]*i)%p;
    		inv[p-1]=quickpow(fac[p-1],p-2);
    		for(int i=p-1;i>=1;i--)
    			inv[i-1]=(inv[i]*i)%p;
    		inv[0]=1;
    		printf("%lld\n",lucas(n+m,m));
    	}
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值