【learning】 扩展lucas定理
首先说下啥是lucas定理:
(nm)≡(n%Pm%P)×(n/Pm/P)(modP)
借助这个定理,求(nm)时,若P较小,且n,m非常大时,我们就可以用这个定理要降低复杂度。
但是这个定理有一些限制,比如说要求p是质数,遇到一些毒瘤出题人不太好应对。
当P不是质数时,这时就要用到一个叫做扩展lucas定理的东西。
令P=∏pkii。
我们发现,如果对于每一个pkii,我们都求出(nm)%pkii的值,我们就可以用CRT将它们合并,以得到最终的(nm)。
下面考虑如何求(nm)%pkii。
首先考虑组合数的一个性质:
(nm)=n!m!(n−m)!
那么问题就可以归化为求n!及其逆元的问题
我们发现,我们可以考虑求出n!,m!的逆元,(n−m)!的逆元,然后就可以求出组合数了。
直接求的话,会发现m!和(n−m)!可能求不出逆元,因为m!可能会与pkii不互质。
我们定义G(n,pi)表示n!中素因子pi的个数,定义F(n,pi,ki)=n!pG(n,pi)i。
则有:
(nm)=n!m!(n−m)!≡F(n,pi,ki)pG(n,pi)iF(m,pi,ki)pG(m,pi)i×F(n−m,pi,ki)pG(n−m,pi)i(modpkii)
考虑如何求函数F,我们显然有一种O(n)的求法:
F(n,pi,ki)≡n∏x=1,(x,pi)=1x×F(n/pkii,pi,ki)(modpkii)
但是它依然是O(n)的
通过简单观察可以知道,求解F的连乘过程中有关于pkii的循环节,我们可以求出循环节的积,然后通过快速幂求解出前面若干个循环节的积的幂,最后乘上末尾非循环节部分的数。
举个例子:当n=19,p=3,k=2时:
F(19,3,2)≡1×2×4×5×7×8×10×11×13×16×17×19×F(6,3,2)(modpkii)
≡(1×2×4×5×7×8)2×19×(modpkii)
根据这一个性质,我们得到:
F(n,pi,ki)≡(pkii∏x=1,(n,pi)=1)⌊n/pkii⌋F(n/pkii,pi,ki)(modpkii)
当n=0时,F(n)=1。
考虑如何求函数G,我们同样地采用递归的方式来搞,当n>0时,有:
G(n,pi)=⌊npi⌋+G(⌊npi⌋,pi)
当n=0时,G显然为0。
至此,我们求出了(nm)%pkii。
我们求出了若干组这样的方程后,用CRT合并,就得到了最终的答案。
这种做法的复杂度也是非常地玄学,它是:
O(∑pklog(logpn−k)+plogP)
1 #include<bits/stdc++.h> 2 #define M 20000005 3 #define L long long 4 #define INF (1LL<<60) 5 using namespace std; 6 7 L pow_mod(L x,L k,const L MOD){L ans=1;for(;k;k>>=1,x=x*x%MOD) if(k&1) ans=ans*x%MOD; return ans;} 8 9 void exgcd(L a,L b,L &x,L &y){ 10 if(!b) {x=1; y=0; return;} 11 exgcd(b,a%b,y,x); 12 y-=a/b*x; 13 } 14 L inv(L a,L MOD){ L res1,res2; exgcd(a,MOD,res1,res2); return (res1+MOD)%MOD;} 15 16 L getp(L n,L p){L ans=0; for(;n;n/=p) ans=ans+n/p; return ans;} 17 L fac(L n,L p,L k){ 18 if(!n) return 1; 19 L all=pow_mod(p,k,INF),mul=1,ans=1; 20 for(L i=1;i<all;i++) if(i%p) mul=1LL*mul*i%all; 21 ans=pow_mod(mul,n/all,all); 22 for(L i=n%all;i;i--) if(i%p) ans=1LL*ans*i%all; 23 return 1LL*ans*fac(n/p,p,k)%all; 24 } 25 26 L get(L n,L m,L MOD){ 27 L c1=0,m1=1,p,up; 28 for(p=2,up=sqrt(MOD);p<=up;p++) if(MOD%p==0){ 29 loop:; 30 L k=(p>up),all=(p>up?p:1); 31 while(MOD%p==0) k++,MOD/=p,all*=p; 32 L facn=fac(n,p,k); 33 L facm=fac(m,p,k); 34 L facnm=fac(n-m,p,k); 35 L psum=getp(n,p)-getp(m,p)-getp(n-m,p); 36 L c2=1LL*facn*inv(facm,all)%all*inv(facnm,all)%all*pow_mod(p,psum,all)%all; 37 L mm=m1*all; 38 L x=(1LL*inv(m1,all)*(c2-c1)%mm*m1+c1)%mm; 39 m1=mm; c1=(x+m1)%m1; 40 } 41 if(MOD>1){p=MOD; MOD=1; goto loop;} 42 return c1; 43 } 44 45 main(){ 46 L z,y,p; cin>>z>>y>>p; 47 cout<<get(z,y,p)<<endl; 48 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!