OI数论简陋入门:从入门到退役
数论笔记
前言
本文主要基本地介绍知识点,通常附有证明,可能并不太系统,顺序可(一)能(定)也有一些问题(记得看目录)
本文主要需要的前置芝士:同余,gcd,lcmgcd,lcm,乘法逆元,线性筛,剩余系,简化剩余系,导数
本文主要讲述了(不完全按顺序):(扩展)欧拉定理,费马小定理,数论函数的定义,常用积性函数及其变换,狄利克雷卷积,莫比乌斯反演,杜教筛,min_25筛,Powerful Number筛
有时间将补充:Pollard Rho,Pohlig-Hellman
本文篇幅较长,请耐心阅读,如有错误,欢迎指出
定义
一些规定
1.如无特殊标记,fk(n)=fk(n)fk(n)=fk(n)
2.如无特殊说明,num(n,p)=max(k∈pk|n)num(n,p)=max(k∈pk|n)
3.[x][x]按照语境通常是艾弗森符号的含义或表示闭区间,有时也作向下取整(我尽量用最规范的),{x}{x}表示取小数部分,有时也作集合使用
4.pp表示容易质数,PP表示质数集,如无特殊说明,pipi表示第ii大的质数
5.lpf(x)lpf(x)表示xx的最小质因子
6.δm(a)δm(a)表示aa关于mm的阶
7.使用勒让德符号,(n|p)≡np−12(modp)(n|p)≡np−12(modp)
许多数论函数
素数个数函数
欧拉函数φ(x)φ(x):[1,n] 中与n互质的数的个数
刘维尔函数(不是积性函数)
莫比乌斯函数
约数个数函数
元函数
恒等函数
单位函数
约数和函数
一维前缀和
自定义的几个常用函数:
证明放在后面
积性函数:
完全积性函数:
狄利克雷卷积
定义
设f,gf,g为积性函数
则f∗g=∑d|nf(d)g(nd)f∗g=∑d|nf(d)g(nd)
狄利克雷卷积的一些性质
f∗ϵ=ff∗ϵ=f
f∗g=g∗ff∗g=g∗f
f∗(g∗h)=(f∗g)∗hf∗(g∗h)=(f∗g)∗h
(f+g)∗h=f∗h+g∗h(f+g)∗h=f∗h+g∗h
两个积性函数卷完之后还是积性函数
上述结论均不难证明,在此略过
定理
欧拉定理&扩展欧拉定理
特殊地,gcd(a,p)=1gcd(a,p)=1时,有ak≡akmodφ(p)(modp)ak≡akmodφ(p)(modp)
注意,使用拓展欧拉定理时应当判断kk与φ(p)φ(p)的大小
欧拉定理的证明
考虑证明简单一点的情况:
不难发现这和特殊情况是等价的(其实就是欧拉定理)
考虑pp的简化剩余系(似乎也有叫缩系的)x1,x2⋯xφ(p)x1,x2⋯xφ(p)
构造数列yi=axiyi=axi
引理
yy也是pp的简化剩余系
引理证明
发现简化剩余系有两个主要性质:与原数互质,%pp后两两不等
考虑分别证明
证明与原数互质
由简化剩余系的定义
再证明%pp后两两不等
有
结合两条性质不难证明引理
有了引理我们不难发现,yy和xx只是在次序上不同
所以
得证
拓展欧拉定理的证明
同余的基本性质告诉我们
拓展欧拉定理写成如下形式
令
发现只需要证明对于每一个pqiipqii拓展欧拉定理成立,对于mm,扩展欧拉定理就也成立
若
那么就是欧拉定理的情形,只需考虑gcd(a,pqii)≠1gcd(a,pqii)≠1的情况
即pipi是质数,所以a=npia=npi
引理
φ(pk)≥kφ(pk)≥k
考虑k=0,1k=0,1时带入验证即可
对于k>1k>1的情况,两边同时求导,即左边的导数为l′l′,右边的导数为r′r′
得到
所以不等式在k>1k>1时恒成立
引理得证
有
所以
于是gcd(a,pqii)≠1gcd(a,pqii)≠1的情形也就得证了
证毕
费马小定理
其实就是欧拉函数的性质在欧拉定理中的应用:
证明略过
顺带一提,并不是所有合数都不符合费马小定理,但是费马小定理可以用来初步判断素数
中国剩余定理&扩展中国剩余定理
描述了这样一个构造方法
代入即可发现这样的 xx 是一个解,于是就可以求出 xmodMxmodM 了,可以用来求解大数取模
扩展中国定理(EXCRT)中则不需要 ∀i,j gcd(mi,mj)=1∀i,j gcd(mi,mj)=1 这一条件
此时没有人类智慧的构造方法了
观察性质,记 LCM=lcm(m1,m2…mn)LCM=lcm(m1,m2…mn) 最后的答案一定是
于是考虑合并方程,直至最后剩下一个作为通解
已知
则
注意到 m1,m2,r1,r2m1,m2,r1,r2 都已知,运用 ex gcdex gcd 即可求出一组 k1,k2k1,k2,代回即可求出通解
同理消去方程即可解出通解,中间任何一次合并的无解都会导致最终无解
素数定理
π(x)∼xlnxπ(x)∼xlnx
不太重要,不过证明一些复杂度时需要用到,这里给出简略的初等证明(不用ζζ函数和复变换)
考虑从离散到连续的变化,又可以写作
注意到:
p∣n!p∣n!当且仅当p≤np≤n
记
那么有
可以注意到
这似乎并不可做,但可以放缩
取对数得
用斯特林公式(或许以后会在多项式的文章里证明,不过不会也没什么关系)拟合n!n!:
于是,我们有
记
不难发现x≥2x≥2时,lnxx−1lnxx−1是减函数
于是
设
则
那么
有
于是
对ii进行求和
因为gg是减函数,且恒大于00,所以g(p1)−g(pt)g(p1)−g(pt)是收敛的
于是
两边同时求导得
于是
最后一步的变换用洛必达就可以了
证毕
拉格朗日定理
f(x)是模p意义下的整系数n次多项式,f(x)是模p意义下的整系数n次多项式,f(x)≡0(mod p)至多有n个不同解f(x)≡0(mod p)至多有n个不同解
考虑归纳法
n=0n=0时显然成立
若f(x)≡0(mod p)f(x)≡0(mod p)有n+1n+1个不同解,依次记为x0,x1,x2...xnx0,x1,x2...xn
有
于是注意到g(x)g(x)有x1,x2,x3...xnx1,x2,x3...xn共 nn 个根,矛盾
证毕
二次探测定理
p∈P∧p≠2⇒x2≡1(mod p)的解只有±1p∈P∧p≠2⇒x2≡1(mod p)的解只有±1
原方程可化为
又有 p∈Pp∈P
于是得证
小知识
向下取整小结论
其中a,b,na,b,n均为正整数
证明如下:
神秘小知识
证明如下:
构造一个二元数列(h,k)(h,k)使得(h,k)=1∧k∣n∧1≤h<k(h,k)=1∧k∣n∧1≤h<k,特殊地,(1,1)(1,1)也被认为是合法的
构造另一个二元数列(i,n)(i,n)其中1≤i≤n1≤i≤n,
对于(h,k)(h,k)考虑其到(i,n)(i,n)的映射,
不难发现(h,k)→(hnk,n)(h,k)→(hnk,n)
可以得到|(h,k)|≤|(i,n)||(h,k)|≤|(i,n)|
再考虑(i,n)(i,n)到(h,k)(h,k)的映射
可以发现|(i,n)|≤|(h,k)||(i,n)|≤|(h,k)|
放缩得|(i,n)|=|(h,k)||(i,n)|=|(h,k)|
显然有|(i,n)|=n|(i,n)|=n,所以|(h,k)|=n|(h,k)|=n
结合定义知k=d⇒(h,k)=φ(d)k=d⇒(h,k)=φ(d),即|(h,k)|=∑d∣nφ(d)|(h,k)|=∑d∣nφ(d)
得证
结合定义不难证得
μμ的最重要性质
说人话就是μμ是II的反函数
证明如下
若n≠1→n=∏paiin≠1→n=∏paii
由μμ的定义知,若∃num(m,p)>1→μ(m)=0∃num(m,p)>1→μ(m)=0
故而我们只考虑λ(n)∏i=1[num(m,pi)≤1]=1λ(n)∏i=1[num(m,pi)≤1]=1的mm,下面的mm都满足这一性质
设
就是说SiSi考虑有ii个质因子的mm的总贡献,LiLi表示这样的mm的个数,特殊地,我们有L0=S0=1L0=S0=1,也就是m=1m=1的贡献
由组合数的知识容易得到
考虑用LiLi来表达I∗μI∗μ,简单带入可得
考虑构造函数
所以有
综上,得证
gcd(n,m)=1→φ(nm)=φ(n)φ(m)gcd(n,m)=1→φ(nm)=φ(n)φ(m)也就是说欧拉函数是积性函数,但不是完全积性函数
莫比乌斯反演
证明如下
得证
通常在使用时遵循如下方式
交换求和号后暴力莫反即可
原根
定义
首先定义“阶”。$ a,关于关于,m,$的阶是
其中,δm(a)δm(a)表示 aa 关于 mm 的原根
神秘定理
一个数 mm 的最小原根 g≤m14g≤m14
我不会证
一些引理
引理1
gcd(a,m)=gcd(b,m)=1时δm(a)δm(b)=δm(ab)⇔gcd(δm(a),δm(b))=1gcd(a,m)=gcd(b,m)=1时δm(a)δm(b)=δm(ab)⇔gcd(δm(a),δm(b))=1
先证,δm(a)δm(b)=δm(ab)⇒gcd(δm(a),δm(b))=1δm(a)δm(b)=δm(ab)⇒gcd(δm(a),δm(b))=1
结合定义可得
再证,gcd(δm(a),δm(b))=1⇒δm(a)δm(b)=δm(ab)gcd(δm(a),δm(b))=1⇒δm(a)δm(b)=δm(ab)
考虑如下变形
同理可得
又有
已知gcd(δm(a),δm(b))=1gcd(δm(a),δm(b))=1
于是
即
证毕
引理2
gcd(a,m)=1,δm(ak)=δm(a)gcd(δm(a),k)gcd(a,m)=1,δm(ak)=δm(a)gcd(δm(a),k)
给个式子的大写
代入后易得右边是 δm(ak)δm(ak) 的倍数
只需证明左边也是右边的倍数
有
于是
得证
引理3
∀gcd(a,m)=gcd(b,m)=1,∃c s.t.δm(c)=lcm(δm(a),δm(b))∀gcd(a,m)=gcd(b,m)=1,∃c s.t.δm(c)=lcm(δm(a),δm(b))
考虑如下构造(反正我考虑不到)
可以发现符合要求,得证
存在原根的判断
结论:m=1,2,4,pa,2pam=1,2,4,pa,2pa 时才有原根
m=1,2,4m=1,2,4 时易得证
先考虑奇素数的情况
考虑拉格朗日定理
又由费马小定理
可得
于是,nn为pp的一个原根
设 gg 为 pp 的一个原根,由归纳法可知
由欧拉定理得
设 1≤b≤a,δpa(g)=pb−1(p−1)1≤b≤a,δpa(g)=pb−1(p−1)
考虑到
这样就得到了所有满足条件的情况,接着需要证明其他情况都无原根
p=2rp=2r 时,gg 只能为奇数
无原根
否则,p=rt s.t.gcd(r,t)=1p=rt s.t.gcd(r,t)=1
无原根
综上,只有m=1,2,4,pa,2pam=1,2,4,pa,2pa 时,mm 有原根
原根的求法
根据经验(我不会证),最小原根不是多项式级别的,所以可以暴力找最小的原根
显然,如果 gg 是 mm 的原根,下面是充要条件
复杂度大概是Θ(m14logmloglogm)Θ(m14logmloglogm),m14m14 是最小原根的上界,logmlogm 是快速幂,loglogmloglogm是枚举因子
然后有
就可以求出所有原根了,证明以后有时间再补上吧,顺便我们还可以得到 mm 的原根数目为φ(φ(m))φ(φ(m))
积性函数的一些性质
通用性质
对于任意积性函数ff有
有算数基本定理和积性函数的定义容易推出
对于任意完全积性函数ff有
若积性函数ff满足上式,则ff也是完全积性函数
μμ与φφ的关联
由结论1得:
考虑消去其中的II,利用结论2,两边同卷μμ:
一些自定义函数
φ2φ2
ϕ2
一点补充
暴力展开即可得证,读者自证不难
算法
卢卡斯定理&拓展卢卡斯定理
算法
所以为什么叫定理啊
不妨直接说 exLucas 定理,可以用来解决
Cmnmodp
其中,p 可以是任意正整数
不妨直接展开
如果 p 的质因子足够大,就可以直接求 m!(n−m)! 的逆元
如果 p 比较小,我们考虑
注意到
原问题就转化为了求
之后有
使用中国剩余定理求解即可得到 Cnmmodp,复杂度为 Θ(λ(p))
发现,只要 Cmn 中没有 p 这个质因子,就可以直接求逆元
这时,一个朴素的思想是把其中的 p 都提出来,最后再乘回去,即
只需要处理 c1−c2−c3≠0 的情况
转化为处理如下情形
发现并没有很好的方法,于是考虑一次只除去一个 p ,也就是说将 1∼n 的所有 p 的倍数都除去一个 p
注意到这样的数有 ⌊n!p⌋ 个
除去 p 之后,有
显然可以递归直到 ⌊n!p⌋<p
再观察 n! 中不含 p 的项,有
于是可以发现循环节,最后一节可以不完整,但长度不超过 pk,所以这一步的复杂度是Θ(pk+lognpk)
因为 p 已经被提出来了,所以最后的结果就是
注意其中每一部分计算时都需要忽视 p (因为需要提出来放在外面处理)
这样就解决了问题,有一个简洁的式子,但是我忘了
复杂度
p 的质因子个数为 λ(p),又有 Θ(λ(p))=Θ(loglogp)
接着分析计算 n!pαmodpk 的复杂度
设其复杂度为 Θ(fp(n)),则
至多递归 logpn 次,于是有
记
则
再全部加起来
左边不太可能是瓶颈,只考虑右边
这样放缩显然过于粗略(但确实是界,p=2α 时取到),一般估计时可以采用作为近似的下界 Θ(p1logloglogploglogplogplogn)
真的下界可能是 Θ(p1loglogploglogplogplogn)
是假设有 loglogp 个等大的因数得出的
代码
namespace Lucas
{
inline int pow(ll a,ll x,int p)
{
if(a>=p) a%=p;
if(a==1) return 1;
if(a==0) return 0;
if(a==-1) return (x&1)?-1:1;
int as=1,ds=a;
while(x)
(x&1)&&(as=(ll)as*ds%p),
ds=(ll)ds*ds%p,
x>>=1ll;
return as;
}
inline int exgcd(int a,int b,int &x,int &y)
{
if(!b)
{
x=1,y=0;
return a;
}
int t=exgcd(b,a%b,y,x);
y-=a/b*x;
return t;
}
inline int p_num_of_fact(ll n,int p)
{
int as=0;
for(;n>=p;n/=p) as+=n/p;
return as;
}
inline int mod_fact(ll n,int p,int mod)
{
if(!n) return 1;
int as=1;
for(int i=2,md=2;i<=mod;++i,++md)
if(md==p) md=0;
else as=(ll)as*i%mod;
as=pow(as,n/mod,mod);
for(int i=2,md=2,ed=n%mod;i<=ed;++i,++md)
if(md==p) md=0;
else as=(ll)as*i%mod;
return (ll)as*mod_fact(n/p,p,mod)%mod;
}
inline int a_choose(ll n,ll m,int p,int mod)
{
if(m>n) return 0;
int c1(p_num_of_fact(n,p)),c2(p_num_of_fact(m,p)),c3(p_num_of_fact(n-m,p));
int as=1,tmp=0;
if(c1-c2-c3>0)
if((tmp=pow(p,c1-c2-c3,100000000))>=mod) return 0;
else as=tmp%mod;
else tmp=0;
int a1(mod_fact(n,p,mod)),a2(mod_fact(m,p,mod)),a3(mod_fact(n-m,p,mod));
if(as^1) a1=(ll)a1*as%mod;
int x(0),y(0);
exgcd(a2*a3%mod,mod,x,y);
return (ll)a1*x%mod;
}
inline int exLucas(ll n,ll m,int p)
{
static int a[110],b[110],cnt;
int x=p;
for(int i=2,k=sqrt(x)+1,tmp=1;i<=k;++i)
if(p%i==0)
{
for(tmp=1;x%i==0;) tmp*=i,x/=i;
a[++cnt]=tmp,b[cnt]=a_choose(n,m,i,tmp);
}
if(x^1) a[++cnt]=x,b[cnt]=a_choose(n,m,x,x);
int A=a[1],B=b[1];
for(int i=2,k1=0,k2=0;i<=cnt;++i)
{
int g=exgcd( A,a[i],k1,k2);
int t=A;
A=(ll)A*a[i]/g;
B=(ll)((ll)t*k1%A*(ll)(b[i]-B)/g%A+B)%A;
}
return (A+B)%A;
}
}
BSGS&exBSGS
ax≡b(mod p),求 x
显然最小的答案不会超过 p(只有 p 种余数,之后一定会循环),于是可以做到 O(p)
考虑根号分治
于是可以枚举 k,把对应的值插到一个hash表里,然后枚举 l,判断hash表中有没有对应的值即可
于是做到 O(√p)(严格来说是 O(√φ(p)))
如果 gcd(a,p)>1,那么不能直接采用上面的方法,因为求不出逆元
此时需要exBSGS
显然,b=1⇒x=0
考虑其他情况,设
则
有解的必要条件是 bg∈Z
最后一个式子变换得
有
故 a′ 在模 p′ 的意义下一定存在逆元,然后用 BSGS 就可以了
另一种方便的方法是直接做 BSGS,枚举 −l 即可,然后代回验证即可,不过细节挺多的
Miller-Rabin
由费马小定理
发现这样的运算是 O(log p) 的,但符合上式的 p 也不一定是素数,于是用二次探测定理二次验证
二次验证时注意到 p−1 一定是偶数,于是取 p2 继续检测,结果只能是 ±1
这样做,一次的错误概率是 14,可以接受
如果广义黎曼猜想成立,只需取前⌊2(log n)2⌋个数进行费马小定理的检验即可确定性地判素
虽然我们不难知道广义黎曼猜想是否成立,但在 278 的范围内只需要取 2,3,5,7,11,13,17,19,23,29,31,37 即可确定性的判素
namespace math
{
inline ll rand_num() {return (ll)rand()*rand()*rand()+(ll)rand()*rand();}
inline ll pow(ll a,ll x,ll p)
{
if(a>=p) a%=p;
if(a==1) return 1;
if(a==0) return 0;
if(a==-1) return (x&1)?-1:1;
ll as=1,ds=a;
while(x)
(x&1)&&(as=(ll)as*ds%p),
ds=(ll)ds*ds%p,
x>>=1ll;
return as;
}
inline bool is_prime(ll n)
{
int tab[12]={2,3,5,7,11,13,17,19,23,29,31,37};
for(int i=0;i<12;++i)
if(n==tab[i]) return true;
else if(n%tab[i]==0) return false;
for(int i=0;i<12;++i)
if(pow(tab[i],n-1,n)!=1)
return false;
return true;
}
}
杜教筛
杜教筛的构造
要求f的前缀和,构造一个很好求前缀和的函数h和一个很好求前缀和的辅助函数g,使得
按狄利克雷卷积的定义展开
考虑用Sh表示S,于是考虑交换求和符号,使得g和f分离:
发现n1=n,所以我们把右边第一项提出来:
整理得
由于构造的Sh(n)是容易求得的,即可以在低于线性的时间内求出(实际上很多时候都是Θ(1)的),
g(1)也显然可以在Θ(1)的时间里求出,所以只需要在低于线性的时间里求g(d)S(nd)的除去第一项的前缀和
考虑使用整除分块,则转化为求g在不超过2√n段上的和,前缀和即可,
对于S([nd])项,我们直接递归,暴力地预处理出n23个前缀和即可
复杂度分析
下文考虑中均认为求f,g的前缀和是Θ(1)的
运用杜教筛,我们把求S(n)转化为了求所有的S(nd),本质上只有2√n种取值,忽略常数后,我们设杜教筛的复杂度为T(n),可得方程
含义为,求前n项前缀和的复杂度是整除分块的复杂度+处理出每个S(nd)和S(d)的复杂度
这里我们默认了n超出了预处理的前缀和,但nd显然可能是被预处理过的值,所以需要考虑预处理,设预处理了k(k≥√n)个,那么总的复杂度为:
注意到所有T(i)都已经被预处理了,可以Θ(1)得到,复杂度为Θ(√n),
接着考虑T(ni)的处理,考虑继续展开,注意此时不需要再预处理了:
上式最后的∑含义为一个大于k的值迭代到k以内的迭代次数
令总复杂度为D=k+T(n),选k为主元,考虑其极值,得
最终的复杂度为Θ(n23).
min25筛
求函数F(x)的前缀和,F(pk)=pol(pk),pol是多项式函数
对于部分函数,我们容易构造出杜教筛解决,但如果辅助函数不好构造或者干脆不是积性函数,就不能杜教筛了,此时,我们可以考虑min25筛(当然min_25筛对于函数的性质还是有要求的,一般不是积性函数的话,合数部分贡献的计算很好处理,下文只考虑是积性函数的情况)
计算方法
对于一个多项式,我们可以把每一项的贡献分开考虑,而常数显然可以最后再乘,所以我们只需要考虑F(p)=pk的情况
考虑一个更简单的问题
求∑p≤npk
不能线筛的话似乎毫无办法,但这里可以考虑dp,记
也就是说g(n,x)表示区间[1,n]中质数或最小质因子比第x大质数大的数的k次方的贡献
怎么转移呢?
我们考虑容斥,显然答案会减小,失去贡献的是最小质因子恰好为pj的合数,又发现pk这种幂函数是完全积性的
所以选定一个pj除掉,再补上多减的,可以得到转移方程:
答案就是g(n,π(n)),可是,这还是线性的呀?
但是,我们发现
于是最后的g(pj−1,j−1)就可以Θ(√n)线筛预处理了
至于前面的部分,之后再说明
回到原问题,我们把贡献拆成质数和合数两部分,然后枚举合数的lpf及其次数
设S(i,j)表示前i个数中的质数或最小质因子不小于pj的合数的贡献
即
不难发现,边界条件为
最终答案就是
这时,我们发现我们只需要求S(n,1),于是我们求的g形如
可以发现只需要求Θ(√n)个g的值就可以了
复杂度分析
我们发现min_25筛干了这些事:求Θ(√n)个g的值,求Θ(√n)个关键点的g值,求S(n,1)
第一个可以线筛,所以瓶颈在后两个上
由素数定理,质数个数约为nlnn
联想埃筛,可以发现一个数x被筛的次数是Θ(√xlog√x)(此处不区分log和ln的区别,主要是为了和其他算法复杂度写法又一致)
于是可以得到方程:
由离散到连续变换:
可以证明左边总是不大于右边,于是
Talk is cheap,show you the code
inline void init()
{
is[1]=1;
for(int i=1;i<=q;++i)
{
if(!is[i])
p[++cnt]=i,
sp1[cnt]=(sp1[cnt-1]+i)%mod,
sp2[cnt]=(sp2[cnt-1]+1ll*i*i)%mod;
for(int j=1;j<=cnt&&p[j]*i<=q;++j)
{
is[i*p[j]]=1;
if(!(i%p[j]))break;
}
}
for(ll l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
w[++tot]=n/l;
g1[tot]=w[tot]%mod;
g2[tot]=g1[tot]*(g1[tot]+1)/2%mod*(2*g1[tot]+1)%mod*inv3%mod;
g2[tot]--;
g1[tot]=g1[tot]*(g1[tot]+1)/2%mod-1;
if(n/l<=q)ind1[n/l]=tot;
else ind2[n/(n/l)]=tot;
}
}
ll k;
inline ll S(ll x,int y)
{
if(p[y]>=x)return 0ll;
k=(x<=q?ind1[x]:ind2[n/x]);
ll as=(g2[k]-g1[k]+mod-(sp2[y]-sp1[y])+mod)%mod;
for(int i=y+1;i<=cnt&&p[i]*p[i]<=x;++i)
{
ll pe=p[i],tmp;
for(int e=1;pe<=x;e++,pe=pe*p[i])
tmp=pe%mod,
as=(as+tmp*(tmp-1)%mod*(S(x/pe,i)+(e!=1)))%mod;
}
return as%mod;
}
int main()
{
read_(n),
q=sqrt(n);
init();
for(int i=1;i<=cnt;++i)
for(int j=1;j<=tot&&(ll)p[i]*p[i]<=w[j];++j)
k=(w[j]/p[i]<=q?ind1[w[j]/p[i]]:ind2[n/(w[j]/p[i])]),
g1[j]-=(ll)p[i]*(g1[k]-sp1[i-1]+mod)%mod,
g2[j]-=(ll)p[i]*p[i]%mod*(g2[k]-sp2[i-1]+mod)%mod,
g1[j]%=mod,
g2[j]%=mod,
(g1[j]<0)&&(g1[j]+=mod),
(g2[j]<0)&&(g2[j]+=mod);
write_((S(n,0)+1)%mod,'\n');
return 0;
}
模板题的代码
Powerful Number 筛
个人觉得是最简单的筛法
先定义powerful number为∀n,∀p∈P∧p∣n,p2∣n
说人话就是所有质因子次数不小于2的数
还是求积性函数的前缀和,构造函数g,g(p)=f(p),并使得g的前缀和容易求出
令
有
考虑展开卷积,得
考虑g的前缀和是容易求出的,交换求和号
这依然是Θ(n)的,但因为h是积性函数并且h(p)=0,所以∀h(n)≠0,n∈Powerful Number
考虑Powerful Number的数量,以下的q是Powerful Number
由定义知
上文的pi仅表示q的质因子
不难发现
那么
记power(n)=[n∈Powerful Number],那么powerful number的数目就是power()的前缀和
考虑枚举A,计算对于的贡献,为了不算重,应该只枚举1−√n,那么
那么就只需要求出[1,n]中的所有powerful number,然后就可以Θ(F√n)求出f的前缀和,其中F是求g的前缀和的复杂度
接下来介绍一个小技巧:
观察f=g∗h,发现f∗g−1=ϵ∗h=h,其中g−1∗g=ϵ
所以只要构造出g就可以求出h,减小了构造的工作量
powerful number筛的时间复杂度和思维难度都有一定优势,但其辅助函数难以构造,使得很多问题不能用其解决
代码
封装好了一些基本的东西
卢卡斯定理没有过
class MyMath
{
public:
vector<int> prime;
vector<int> phi;
vector<int> mu;
template<typename Tp_> inline Tp_ inv(Tp_ x,Tp_ p) {return pow(x,-1,p);}
inline bool is_prime(ll x)
{
if(x<0) x=-x;
if(!prime.empty())
if((*prime.rbegin())*(*prime.rbegin())>=x)
{
for(vector<int>::iterator it=prime.begin();(*it)*(*it)<=x&&it!=prime.end();++it)
if(!(x%(*it))) return false;
return true;
}//surely a bit faster,almost log(n)
for(ll i(2);i*i<=x;++i)
if(!(x%i)) return false;
return true;
}//n^0.5 hardly used
inline ll pow(ll a,ll x,ll p)
{
if(x==0) return 1;
if(x>0)
{
ll ret=1;
while(x) ret=(x&1?a*ret%p:ret),x>>=1,a=a*a%p;
return ret;
}
else
{
if(!phi.empty())
if(phi.size()>p)
return pow(a,x%phi[p]+phi[p],p);
ll ph=Phi(p);
return pow(a,x%ph+ph,p);
}
}//maybe log,n^0.5 when using Phi()
template <typename Tp_> inline Tp_ gcd(Tp_ x,Tp_ y)
{
if(x>y) swap(x,y);
if(x==0) return (y?y:1);
return gcd(y%x,x);
}//almost log
template <typename Tp_> inline Tp_ lcm(Tp_ x,Tp_ y) {return x/gcd(x,y)*y;}
inline int ex_gcd(int a,int b,int c,int &p,int &q)
{
int g=ExEuclid(a,b,p,q);
if(c%g) return p=0,q=0;
g=c/g,p*=g,q*=g;
return 1;
}//a*p+b*q=c(mod p),about log,maybe
template<typename Tp_> inline Tp_ Phi(Tp_ x)
{
Tp_ tmp=x,ret=x;
for (Tp_ i=2;i*i<=x;++i)
if (tmp%i==0)
{
ret=ret-ret/i;
while (tmp%i==0) tmp/=i;
}
if(tmp>1) ret-=ret/tmp;
return ret;
}//n^0.5
template<typename Tp_> inline Tp_ Lucas(Tp_ n,Tp_ m,Tp_ p)
{
if(m==0) return 1;
Tp_ np=n%p,mp=m%p;
if(np<mp) return 0;
mp=min(np-mp,mp);
Tp_ p1=1,p2=1;
for( Tp_ i = 0 ; i < mp ; ++i )
p1=p1*(n-i)%p,
p2=p2*(i+1)%p;
return (p1*pow(p2,p-2)%p)*Lucas(n/p,m/p,p)%p;
}//p must be a prime
inline void sieve(const int capN)
{
bool *isp;
isp=new bool [capN+5];
memset(isp,0,sizeof(bool)*(capN+4));
if(!prime.empty()) prime.clear();
for(int i(2);i<=capN;++i)
{
if(!isp[i]) prime.emplace_back(i);
for(vector<int>::iterator it=prime.begin();it!=prime.end();++it)
{
if(i*(*it)>capN) break;
isp[i*(*it)]=1;
if(!(i%(*it))) break;
}
}
delete isp;
}//O(n) and need more place
inline void phi_sieve(const int capN)
{
if(!prime.empty()) prime.clear();
phi.resize(capN+4,0),phi[1]=1;
for(int i(2);i<=capN;++i)
{
if(!phi[i])
prime.emplace_back(i),
phi[i]=i-1;
for(vector<int>::iterator it=prime.begin();it!=prime.end();++it)
{
if(i*(*it)>capN) break;
if(!(i%(*it)))
{
phi[i*(*it)]=phi[i]*(*it);
break;
}
else phi[i*(*it)]=phi[i]*(*it-1);
}
}
}//cna get phi while finding primes in [1,capN]
private:
inline int ExEuclid(int a,int b,int &p,int &q)
{
if(b==0) return p=1,q=0,a;
int d=ExEuclid(b,a%b,p,q);
int tmp=p;
p=q,q=tmp-a/b*q;
return d;
}//a help function of ex_gcd
}M;
感悟
就是瞎扯,主要是一些基本的思路:
即是最小的,又是最大的,就是唯一的;
先考虑是否存在,再考虑其他性质;
不知道怎么做的时候可以考虑用近似公式拟合;
日志
upd 2021.11.5更新了格式和筛法
upd 2022.2.18更新了原根
upd 2022.2.19 exLucas定理
upd 2022.3.25 EXCRT
后记
咕咕咕,咕~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现