扩展卢卡斯定理(Exlucas)

题目链接

戳我

前置知识

  1. 中国剩余定理(crt)或扩展中国剩余定理(excrt)
  2. 乘法逆元
  3. 组合数的基本运用
  4. 扩展欧几里得(exgcd)

说实话Lucas真的和这个没有什么太大的关系,但是Lucas还是要学学的:戳我

正文

题目是要求:

\[c_n^m mod \ p \]

如果这个p是质数的话那太简单了,直接Lucas就好了,但问题是现在p不一定是一个质数。

我们令 \(P=\prod {p_i}^{c_i}\)

我们如果知道每个\(c_n^m mod \ p_i^{c_i}\)的值的话就可以根据中国剩余定理求出答案
那我们怎么求出这个值呢?
我们可以将\(c_n^m\)写成\(\frac{n!}{m!(n-m)!}\)

现在我们可以处理阶乘的模。那么如何处理阶乘的模呢?

举个经典例子:
\(p=3,n=19,c=2\)
我们可以吧式子写成这样:

\[(19*18*17*16*15*14*13*12*11*10*9*8*7*6*5*4*3*2*1) \]

\[=(19*17*16*14*13*11*10*8*7*5*4*2*1)*3^6*6! \]

我们可以将他分为几个部分

\[19*(17*16*14*13*11*10)*(8*7*5*4*2*1)*3^6*6! \]

我们会发现对于每一个整的部分如\((8*7*5*4*2*1)\)的模数都是一样的,于是这一块我们可以运用快速幂,而剩余的\(19\)我们可以进行暴力。对于\(6!\)我们可以继续递归求解,那么怎么分组呢
我们可以把每一段的范围定为\(p^c\)。差不多就这样吧。

code

#include<bits/stdc++.h>
#define rg register
#define int long long
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
int read(){
    int x=0,f=1;
    char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-48,c=getchar();
    return f*x;
}
inline void exgcd(int a,int b ,int &x,int &y){
    if(!b){x=1,y=0;return;}
    exgcd(b,a%b,x,y);
    int t=x;
    x=y,y=t-(a/b)*y;
}
inline int inv(int a,int b){
    int x,y;
    return exgcd(a,b,x,y),(x%b+b)%b;
}
inline int ksm(int a,int b,int p){
    int ans=1;
    while(b){
        if(b&1)
            ans=a*ans%p;
        a=a*a%p;
        b>>=1;
    }
    return ans%p;
}
inline int crt(int x,int p,int mod){
    return inv(p/mod,mod)*(p/mod)*x;
}
inline int fac(int x,int a,int b){
    if(!x)
        return 1;
    int ans=1;
    for(int i=1;i<=b;i++)
    if(i%a)
            ans*=i,ans%=b;
    ans=ksm(ans,x/b,b);
    for(int i=1;i<=x%b;i++)
        if(i%a)
            ans*=i,ans%=b;
    return ans*fac(x/a,a,b)%b;
}
inline int C(int n,int m,int a,int b){
    int N=fac(n,a,b),M=fac(m,a,b),Z=fac(n-m,a,b),sum=0;
    for(int i=n;i;i=i/a)
        sum+=i/a;
    for(int i=m;i;i=i/a)
        sum-=i/a;
    for(int i=n-m;i;i=i/a)
        sum-=i/a;
    return N*ksm(a,sum,b)%b*inv(M,b)%b*inv(Z,b)%b;
}
inline void exlucas(int n,int m,int p){
    int t=p,ans=0;
    for(int i=2;i*i<=p;i++){
        int k=1;
        while(t%i==0)
            k*=i,t/=i;
        ans+=crt(C(n,m,i,k),p,k),ans%=p;
    }
    if(t>1)
        ans+=crt(C(n,m,t,t),p,t),ans%=p;
    printf("%d",ans%p);
}
main(){
    int n=read(),m=read(),p=read();
    exlucas(n,m,p);
    return 0;
}
posted @ 2019-01-02 20:41  撤云  阅读(878)  评论(1编辑  收藏  举报
……