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

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

1. 拓展卢卡斯定理
  1. 拓展卢卡斯定理是在 m o d mod mod不是质数但是将 m o d mod mod质因数分解后得到 p 1 k 1 p 2 k 2 ⋯ p n k n p_1^{k_1}p_2^{k_2}\cdots p_n^{k_n} p1k1p2k2pnkn 之后, p i k i p_i^{k_i} piki都很小的情况下
    解决 C n m % m o d C_n^m\%mod Cnm%mod的一种方法。

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

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

  4. 算法过程

    1. 假设当前考虑的是 p k p^k pk

    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 ∗ 19 1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19 12345678910111213141516171819

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

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

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

      1. 对于 n ! n! n! 以内是 p p p的倍数的p的幂次方之和, n / p n/p n/p可以得到至少是1次幂之和, n / p 2 n/{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));
    	}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值