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;
}