exLucas 算法学习笔记

一、问题

给定 n,m,p,求 Cnmmodpn,m1018,p106
p 现在不一定是质数了,该怎么办?

二、解法

首先,数论题一个常见的做法:如果模数不一定是质数,那就把模数拆成若干个质数的积,然后分别求解,最后用中国剩余定理求解。

1. 拆 p

我们可以把 p 拆成 a1b1×a2b2××akbk,其中所有 ai 都是质数。
然后对于每一个 aibi,分别求出 Cnmmodaibi,最后再用中国剩余定理合并。

2. 求 Cnmmodab

这等同于求 n!m!×(nm)!modab
但是我们不一定能求出 m!×(nm)! 的逆元,因为 m!×(nm)! 不一定与 a 互质。
所以我们要手动让它们互质。
原式可以表示为 n!axm!ay×(nm)!az×axyzmodab,x=va(n!),y=va(m!),z=va((nm)!)
然后我们可以用 F(n,a) 表示 n!ava(n!),用 G(n,a) 表示 va(n!),那么原式就是 F(n,a)F(m,a)×F(nm,a)×aG(n,a)G(m,a)G(nm,a)modab

3. 求 G(n,a)

这可以用到 Legendre 公式,也就是 a 是质数,va(n!)=i=1n!ai
所以直接按照这个求即可,时间复杂度 O(logn)

4. 求 F(n,a)

由于这个数可能很大,我们求的其实是 F(n,a)modA,其中 A 就是 ab
好像就是 n!×inv(aG(n,a))modA。但是这样我们要预处理出 1!n!,时间复杂度 O(n),超时。
考虑用另一种方法计算。我们注意到 n!=1×2××(A1)×A×(A+1)××(2A1)×2A×(2A+1)××(nA×A1)×(nA×A)×(nA×A+1)××n
由于 F(n,a)a 的倍数全部被去掉(注意 a 是个质数),所以我们假设 H(s,a)=sava(s)。可以发现如果 gcd(a,t)=1,ts,那么 H(s,a)=t×H(st,a)
所以
(1)F(n,a)H(1×2××(A1)×1×(A+1)××(2A1)×2×(2A+1)××(nA×A1)×nA×(nA×A+1))××n,a)(modA)(2)H((1×2××(A1))×1×(1×2××(A1))×2×(1××(A1))×nA×(1××(nmodA)),a)(modA)(3)H(((A1)!)nA×(nmodA)!××nA!,a)(modA)(4)H(((A1)!)nA×(nmodA)!××F(nA,a),a)(modA)(5)((A1)!)nA×(nmodA)!××F(nA,a)(modA)
递归调用 O(logn) 层,提前预处理出 [1(A1)]!modA 每一层 O(logn)(瓶颈在于快速幂),所以总时间复杂度 O(log2n)

5. 总时间复杂度

假设 cp 的不同质因子个数,那么由于 2×3×5×7×11×13×17=1021020>106,所以 c6

  1. 质因数分解,时间复杂度是 O(p)
  2. 对于 i[1,c] 预处理 [1(aibi1)]!modaibi,总时间复杂度是 O(i=1caibi)
  3. F,G,因为至多调用计算函数 c 次,所以这个部分的上界是 O(clog2n)
  4. 用中国剩余定理合并,一共合并 c 次,一次时间复杂度 O(logp),总时间复杂度 O(clogp)

所以总时间复杂度是 O(p+clog2n+clogp+i=1caibi)O(p),并且常数很小。

三、代码

变量名与上文不完全对应。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,m,p,c=0,s[1000010],a[1010],b[1010];
inline void exgcd(ll &x,ll &y,ll a,ll b){
	if(!b){x=1;y=0;return;}
	exgcd(y,x,b,a%b);y-=a/b*x;
}
inline ll ksm(ll a,ll b,ll p){
	ll r=1;
	while(b){if(b&1)r=r*a%p;a=a*a%p;b>>=1;}
	return r;
}
inline ll inv(ll n,ll p){
	ll x,y;
	exgcd(x,y,n,p);
	return (x%p+p)%p;
}
inline ll G(ll n,ll p){
	ll r=0;
	while(n)n/=p,r+=n;
	return r;
}
inline ll F(ll n,ll p,ll pk){
	if(!n)return 1;
	return F(n/p,p,pk)*ksm(s[pk-1],n/pk,pk)%pk*s[n%pk]%pk;
}
inline ll qwq(ll n,ll m,ll p,ll pk){
	s[0]=1;
	for(ll i=1;i<pk;i++)
		if(i%p)s[i]=s[i-1]*i%pk;
		else s[i]=s[i-1];
	ll nowf=F(n,p,pk)*inv(F(m,p,pk)*F(n-m,p,pk)%pk,pk)%pk;
	ll nowg=ksm(p,G(n,p)-G(m,p)-G(n-m,p),pk);
	return nowf*nowg%pk;
}
inline ll exlucas(ll x,ll y,ll p){
	ll r=0,P=p;
	for(ll i=2;i*i<=p;i++)
		if(p%i==0){
			a[++c]=1;
			while(p%i==0)p/=i,a[c]*=i;
			b[c]=qwq(x,y,i,a[c]);
		}
	if(p>1)a[++c]=p,b[c]=qwq(x,y,p,p);
	for(ll i=1,x,y;i<=c;i++)r=(r+inv(P/a[i],a[i])*P/a[i]%P*b[i]%P)%P;
	return r;
}
int main(){
	cin>>n>>m>>p;
	cout<<exlucas(n,m,p)<<'\n';
	return 0;
}
posted @   lrxQwQ  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示