从拓展卢卡斯定理到卢卡斯定理
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} p1k1p2k2⋯pnkn 之后, p i k i p_i^{k_i} piki都很小的情况下
解决 C n m % m o d C_n^m\%mod Cnm%mod的一种方法。 -
由于各个 p i k i 都 互 质 , 我 们 可 以 单 独 考 虑 每 一 个 p i k i p_i^{k_i}都互质,我们可以单独考虑每一个p_i^{k_i} piki都互质,我们可以单独考虑每一个piki,最后再用中国剩余定理合并。
-
考虑到 C n m = n ! m ! ( n − m ) ! C_n^m=\frac {n!} {m!(n-m)!} Cnm=m!(n−m)!n!,我们可以分别求各个的阶乘的答案,再合并起来求 C n m C_n^m Cnm。
-
算法过程
-
假设当前考虑的是 p k p^k pk
-
由于阶乘中有很多数有p这个因子,那么我们得先把p这个因子提出来
-
拿下面的数列做例子,当前考虑的是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 1∗2∗3∗4∗5∗6∗7∗8∗9∗10∗11∗12∗13∗14∗15∗16∗17∗18∗19
变成了
KaTeX parse error: No such environment: align* at position 8: \begin{̲a̲l̲i̲g̲n̲*̲}̲&1 * 2 * 4 * 5 … -
显然可以发现,在同一层 每 p k 每p^k 每pk个构成了一个循环,且每乘 3 3 3的倍数,内部的也是和第一层类似且循环的。
-
统计是 p p p的倍数的p的幂次方之和
- 对于 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}$ 以此类推即可
-
统计不是p的倍数的乘积
- 每一层统计循环的乘积,统计循环了多少次,再统计单独的即可。
-
-
代码
#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. 卢卡斯定理
-
为什么先拓展再本身呢,因为卢卡斯定理和拓展卢卡斯好像没什么肉眼可见的关系
-
做法很简单,就每次将递归后的值乘上对当前n,m取模之后求组合数的值。
-
代码
#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)); } }