Lucas求解组合数取模

Lucas定理求解组合数取模


Lucas定理

让我们先来了解一下Lucas定理。
Lucas定理是用来求C(n,m)%p(p为素数)的值:
C(n,m)%p=(C(n%p,m%p)*C(n/p,m/p))%p
证明如下:

这里写图片描述

C(n,m)的求解

了解了Lucas定理,我们只能将大组合数变成较小的组合数,但是,不能用这个定理化到最简,所以,我们还是需要求解C(n,m)。
C(n,m)=n!/(m!(n-m)!)
我们知道,任何一个数都可以拆分成多个质数相乘的形式,我们设A=n!,P表示质数
A=P1^x1*P2^x2*P3^x3*P4^x4*…*Pk^xk;
同理
m!=B=P1^y1*P2^y2*P3^y3*P4^y4*…*Pk^yk;
(n-m)!=C=P1^z1*P2^z2*P3^z3*P4^z4*…*Pk^zk;
那么怎么快速求出n!中P这个质因子出现了多少次呢?
我们需要用到一个很完美的算法:

int fnd(int x,int p){//求x!中p这个质因子的个数
    int sum=0;//累计个数
    while(x) sum+=x/p,x/=p;//虽然我也不知道为啥可以这样
    return sum;
}

然后再一趟快速幂加mod就可以了。

下面是一段完美的代码

//求C(n,m)%47147(一个素数)
#include<cstdio>
#define LL long long
using namespace std;
const int tt=47147;
LL n,m;int p[100005];
bool vis[100005];
void work(){//线性筛(欧拉筛) 
    vis[0]=vis[1]=1;
    for(int i=2;i<=tt;i++){
        if(!vis[i]) p[++p[0]]=i;
        for(int j=1;j<=p[0]&&i*p[j]<=tt;j++){
            vis[i*p[j]]=1;
            if(i%p[j]==0) break;
        }
    }
}
int fnd(LL x,int y){int sum=0;while(x) sum+=x/y,x/=y;return sum;}//求x!中y这个质因子的个数 
LL qsm(int x,int y){//快速幂,x^y 
    LL ans=1,w=x;
    for(;y;y>>=1){if(y&1) ans*=w;w=w*w;}
    return ans;
}
LL DFS(LL x,LL y){//根据lucas定理,分治的想法 
    if(y==0) return 1;
    if(x>tt) return (DFS(x%tt,y%tt)*DFS(x/tt,y/tt))%tt;//Lucas定理 
    LL ans=1;
    for(int i=1;i<=p[0];i++) ans=(ans*qsm(p[i],fnd(x,p[i])-fnd(y,p[i])-fnd(x-y,p[i])))%tt;//拆分质因子,求解组合数 
    return ans;
}
int main(){
    scanf("%lld%lld",&n,&m);
    work();
    printf("%lld",DFS(n,m));
    return 0;
}

喜欢就加个关注吧

posted @ 2017-12-27 11:32  XSamsara  阅读(151)  评论(0编辑  收藏  举报