学习笔记第二十节:Lucas&扩展Lucas

正题

       Lucas定理大家都会:p\ is\ a\ prime,C_{n}^{m}\equiv C_{n/p}^{m/p} * C_{n\mod p}^{m\mod p}\ \ (\mod p)

long long get_C(long long x,long long y){
    return (fac[x]*inv[x-y]%mod)*inv[y]%mod;
}

long long C(long long x,long long y){
    if(x<y) return 0;
    if(x<mod && y<mod)
        return get_C(x,y);
    return C(x/mod,y/mod)*C(x%mod,y%mod)%mod;
}

       首先,我们先根据唯一分解定理,把p分解为多个质数幂相乘的形式。

       p=\prod_{i=1}^n a_i^{k_i},其中n为不同质因子个数,ai为质数。

       接着我们可以试想一下,如果我们可以求出C_n^m \mod a_i^{k_i},用中国剩余定理合并就完事儿了。

       但是我们不能直接套用Lucas定理,因为a_i^{k_i}可能不是质数。

       发现:C_n^m=\frac{n!}{m!(n-m)!},如果我们可以分别处理出n!\mod a_i^{k_i},m!\mod a_i^{k_i},(n-m)!\mod a_i^{k_i},那就好办了。

       发现n!\mod a_i^{k_i}可以分成三部分。

       我们就拿n=13,a_i=3,k_i=2来举例。

       n!=13!=1\times2\times3\times4\times5\times6\times7\times8\times9\times10\times11\times12\times13 \\=(1\times2\times4\times5\times7\times8\times10\times11\times 13)\times 3^4\times(1\times2\times3\times4)

       一部分是约不了ai的,一部分是约得了ai的,把约不了ai的放在一起,约得了ai的除以ai再放在一起,放ai被除了多少次。

       是不是很和谐,发现:1.n里面能被ai整除的数有n/a_i

       2.前面的数恰好在数值上分为n/a_i^{k_i}组(多出来的单独算),像:1\times2\times 4\times 5\times 7\times8 ,10\times 11\times 13

       那么就很好做了,把前面的分为n/a_i^{k_i}组,我们只需要做一组就可以知道n/a_i^{k_i}组的答案(快速幂即可)。

       中间的可以先不用算,后面的递归处理,多出来的数的个数不会超过a_i^{k_i}个,暴力处理即可。

       那中间的不用算怎么处理呢?,直接用一个k暴力统计n!里面能约出来多少个a_i

for(long long i=n;i!=0;i/=pi) k+=i/pi;
for(long long i=n-m;i!=0;i/=pi) k-=i/pi;
for(long long i=m;i!=0;i/=pi) k-=i/pi;

      这个是很明显的吧。

      然后因为算出来的答案已经去除ai了,那么就可以直接用exgcd(或费马小定理)得出逆元求解,最后再把a_i^k呈上,就是答案了。

      最后中国剩余定理合并。

      

void exgcd(long long a,long long b,long long&x,long long &y){
    if(b==0) {x=1;y=0;return ;}
    exgcd(b,a%b,y,x);
    y-=a/b*x;
}

long long inv(long long a,long long b){
    long long x,y;
    exgcd(a,b,x,y);
    x=(x%b+b)%b;
    if(x==0) return b;
    return x;
}

long long ksm(long long x,long long t,long long mod){
    long long tot=1;
    while(t>0){
        if(t%2==1) (tot*=x)%=mod;
        (x*=x)%=mod;
        t/=2;
    }
    return tot;
}

long long calc(long long x,long long pi,long long pk){
    if(x==0) return 1;
    long long ans=1;
    if(x/pk!=0){
        for(int i=2;i<=pk;i++) 
            if(i%pi!=0) (ans*=i)%=pk;
        ans=ksm(ans,x/pk,pk);
    }
    for(int i=2;i<=x%pk;i++) if(i%pi!=0)(ans*=i)%=pk;
    return ans*calc(x/pi,pi,pk)%pk;
}

long long C(long long n,long long m,long long pi,long long pk){
    long long a=calc(n,pi,pk),b=calc(n-m,pi,pk),c=calc(m,pi,pk);
    long long k=0;
    for(long long i=n;i!=0;i/=pi) k+=i/pi;
    for(long long i=n-m;i!=0;i/=pi) k-=i/pi;
    for(long long i=m;i!=0;i/=pi) k-=i/pi;
    long long ans=((a*inv(b,pk)%pk)*inv(c,pk)%pk)*ksm(pi,k,pk)%pk;
    ans=(ans*(p/pk)%p)*inv(p/pk,pk)%p;
    return ans;
}

int main(){
    scanf("%lld %lld %lld",&n,&m,&p);
    long long x=p,pi,pk;
    long long ans=0;
    for(int i=2;i<=p;i++)
        if(x%i==0){
            pk=1;pi=i;
            while(x%i==0) x/=i,pk*=i;
            ans=((ans+C(n,m,pi,pk))%p+p)%p;
        }
    printf("%lld",ans);
}

      一直觉得这个用逆元来求中国剩余定理很妙,有兴趣的同学可以研究一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值