Typesetting math: 54%

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(kpk|n)num(n,p)=max(kpk|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)np12(modp)(n|p)np12(modp)


许多数论函数

素数个数函数

π(n)=pin1π(n)=pin1

欧拉函数φ(x)φ(x):[1,n] 中与n互质的数的个数

φ(n)=nΠpi1piφ(n)=nΠpi1pi

刘维尔函数(不是积性函数)

λ(n)=dP,d|n1λ(n)=dP,d|n1

莫比乌斯函数

μ(n)={μ(n)=1n=1μ(n)=0a2[a1]|nμ(n)=(1)λ(n)otherwiseμ(n)=μ(n)=1n=1μ(n)=0a2[a1]|nμ(n)=(1)λ(n)otherwise

约数个数函数

d(n)=d|n1d(n)=d|n1

元函数

ϵ(n)=[n=1]ϵ(n)=[n=1]

恒等函数

I(n)=1I(n)=1

单位函数

id(n)=nid(n)=n

约数和函数

σ(n)=d|ndσ(n)=d|nd

一维前缀和

S(n)=n(n+1)2S(n)=n(n+1)2

自定义的几个常用函数:

φ2(n,m)=ni=1mj=1[gcd(i,j)=1]=min(n,m)T=1μ(T)nTmTϕ2(n,m)=ni=1mj=1ij[gcd(i,j)=1]=min(n,m)T=1μ(T)S(nT)S(mT)φ2(n,m)=ni=1mj=1[gcd(i,j)=1]=min(n,m)T=1μ(T)nTmTϕ2(n,m)=ni=1mj=1ij[gcd(i,j)=1]=min(n,m)T=1μ(T)S(nT)S(mT)

证明放在后面

积性函数:

(a,b)=1,f(ab)=f(a)f(b)(a,b)=1,f(ab)=f(a)f(b)

完全积性函数:

a,bZ+,f(ab)=f(a)f(b)a,bZ+,f(ab)=f(a)f(b)


狄利克雷卷积

定义

f,gf,g为积性函数
fg=d|nf(d)g(nd)fg=d|nf(d)g(nd)


狄利克雷卷积的一些性质

fϵ=ffϵ=f
fg=gffg=gf
f(gh)=(fg)hf(gh)=(fg)h
(f+g)h=fh+gh(f+g)h=fh+gh

两个积性函数卷完之后还是积性函数

上述结论均不难证明,在此略过


定理

欧拉定理&扩展欧拉定理

akakmodφ(p)+φ(p)(modp)(k>φ(p))akakmodφ(p)+φ(p)(modp)(k>φ(p))

特殊地,gcd(a,p)=1gcd(a,p)=1时,有akakmodφ(p)(modp)akakmodφ(p)(modp)

注意,使用拓展欧拉定理时应当判断kkφ(p)φ(p)的大小

欧拉定理的证明

考虑证明简单一点的情况:

gcd(a,p)=1aφ(p)1(mod p)gcd(a,p)=1aφ(p)1(mod p)

不难发现这和特殊情况是等价的(其实就是欧拉定理)

考虑pp的简化剩余系(似乎也有叫缩系的)x1,x2xφ(p)x1,x2xφ(p)

构造数列yi=axiyi=axi

引理

yy也是pp的简化剩余系

引理证明

发现简化剩余系有两个主要性质:与原数互质,%pp后两两不等

考虑分别证明

证明与原数互质

由简化剩余系的定义

ijxi>xj,p(xixj)gcd(a,p)=1pa(xixj)p(yiyj)yiyj(mod p)ijxi>xj,p(xixj)gcd(a,p)=1pa(xixj)p(yiyj)yi≢yj(mod p)

再证明%pp后两两不等

xi1(mod p)a1(mod p)yiaxi1(mod p)gcd(yi,p)=1xi1(mod p)a1(mod p)yiaxi1(mod p)gcd(yi,p)=1

结合两条性质不难证明引理

有了引理我们不难发现,yyxx只是在次序上不同

所以

yixi(mod p)axixi(mod p)aφ(p)xixi(mod p)aφ(p)1(mod p)yixi(mod p)axixi(mod p)aφ(p)xixi(mod p)aφ(p)1(mod p)

得证

拓展欧拉定理的证明

同余的基本性质告诉我们

ab0(modp)ab0(modq)ab(modp)ab(modq)ab(modpq)ab0(modp)ab0(modq)ab(modp)ab(modq)ab(modpq)

拓展欧拉定理写成如下形式

bφ(m)abab mod φ(m)+φ(m)(modm)bφ(m)abab mod φ(m)+φ(m)(modm)

m=pqiim=pqii

发现只需要证明对于每一个pqiipqii拓展欧拉定理成立,对于mm,扩展欧拉定理就也成立

gcd(a,pqii)=1gcd(a,pqii)=1

那么就是欧拉定理的情形,只需考虑gcd(a,pqii)1gcd(a,pqii)1的情况

λ(pi)=1piaλ(pi)=1pia

pipi是质数,所以a=npia=npi

引理

φ(pk)kφ(pk)k

考虑k=0,1k=0,1时带入验证即可

对于k>1k>1的情况,两边同时求导,即左边的导数为ll,右边的导数为rr

得到

l=φ(pk)=(pk1(p1))=(pkpk1)lnppk1lnp1r=1ll=φ(pk)=(pk1(p1))=(pkpk1)lnppk1lnp1r=1l

所以不等式在k>1k>1时恒成立

引理得证

φ(p)1φ(pq)varphi(p)(gcd(p,q)=1)φ(p)1φ(pq)varphi(p)(gcd(p,q)=1)

所以

qiφ(pqii)φ(m)bab0(modpqii)ab mod m+φ(m)0(modpqii)abab mod m+φ(m)(modpqii)qiφ(pqii)φ(m)bab0(modpqii)ab mod m+φ(m)0(modpqii)abab mod m+φ(m)(modpqii)

于是gcd(a,pqii)1gcd(a,pqii)1的情形也就得证了

证毕

费马小定理

其实就是欧拉函数的性质在欧拉定理中的应用:

ap1=1(modp)ap1=1(modp)

证明略过

顺带一提,并不是所有合数都不符合费马小定理,但是费马小定理可以用来初步判断素数

中国剩余定理&扩展中国剩余定理

描述了这样一个构造方法

{xa1(mod  m1)xa2(mod  m2)xa3(mod  m3)s.t.1i,jngcd(mi,mj)=1...xan(mod  mn)⎪ ⎪ ⎪ ⎪ ⎪⎪ ⎪ ⎪ ⎪ ⎪xa1(mod  m1)xa2(mod  m2)xa3(mod  m3)s.t.1i,jngcd(mi,mj)=1...xan(mod  mn)

M=ni=1mix=ni=1(Mmi+ai)=ni=1Mmi+ni=1aiM=ni=1mix=ni=1(Mmi+ai)=ni=1Mmi+ni=1ai

代入即可发现这样的 xx 是一个解,于是就可以求出 xmodMxmodM 了,可以用来求解大数取模

扩展中国定理(EXCRT)中则不需要 i,j gcd(mi,mj)=1i,j gcd(mi,mj)=1 这一条件

此时没有人类智慧的构造方法了

观察性质,记 LCM=lcm(m1,m2mn)LCM=lcm(m1,m2mn) 最后的答案一定是

xb(mod LCM)xb(mod LCM)

于是考虑合并方程,直至最后剩下一个作为通解

已知

{xr1(mod m1)xr2(mod m2){xr1(mod m1)xr2(mod m2)

x=k1m1+r1=k2m2+r2k1m1k2m2=r2r1x=k1m1+r1=k2m2+r2k1m1k2m2=r2r1

注意到 m1,m2,r1,r2m1,m2,r1,r2 都已知,运用 ex gcdex gcd 即可求出一组 k1,k2k1,k2,代回即可求出通解

同理消去方程即可解出通解,中间任何一次合并的无解都会导致最终无解

素数定理

π(x)xlnxπ(x)xlnx

不太重要,不过证明一些复杂度时需要用到,这里给出简略的初等证明(不用ζζ函数和复变换)

考虑从离散到连续的变化,又可以写作

π(x)x21lntdtπ(x)x21lntdt

注意到:

pn!pn!当且仅当pnpn

n!=tipriin!=tiprii

那么有

π(n)=tπ(n)=t

可以注意到

ri=k=1npkiri=k=1npki

这似乎并不可做,但可以放缩

ri=k=1npkik=1npki=npi1ri=k=1npkik=1npki=npi1

取对数得

lnn!=nlnpp1lnn!=nlnpp1

用斯特林公式(或许以后会在多项式的文章里证明,不过不会也没什么关系)拟合n!n!:

n!2πn(ne)nlnn!ln(2πn(ne)n)=nln(2n2πnne)=n(lnn1)n!2πn(ne)nlnn!ln(2πn(ne)n)=nln(2n2πnne)=n(lnn1)

于是,我们有

n(lnn1)=nlnpp1lnn1=lnpp1n(lnn1)=nlnpp1lnn1=lnpp1

g(x)=lnxx1g(x)=lnxx1

不难发现x2x2时,lnxx1lnxx1是减函数

于是

0<g(pi+1)<g(pi)0<g(pi+1)<g(pi)

π(x)xp1f(t)dtπ(x)xp1f(t)dt

pi+1pif(x)dx=1x,f(x)>0pixpi+1,f(x)g(pi+1)f(x)g(x)f(x)g(pi)pi+1pif(x)dx=1x,f(x)>0pixpi+1,f(x)g(pi+1)f(x)g(x)f(x)g(pi)

那么

pixpi+1pi+1pif(x)g(pi+1)pi+1pif(x)g(x)pi+1pif(x)g(pi)g(pi+1)pi+1pif(x)pi+1pif(x)g(x)g(pi)pi+1pif(x)pixpi+1pi+1pif(x)g(pi+1)pi+1pif(x)g(x)pi+1pif(x)g(pi)g(pi+1)pi+1pif(x)pi+1pif(x)g(x)g(pi)pi+1pif(x)

pi+1pif(x)dx1pi+1pif(x)dx1

于是

g(pi+1)pi+1pif(x)g(x)g(pi)g(pi+1)pi+1pif(x)g(x)g(pi)

ii进行求和

t1i=1g(pi+1)t1i=1pi+1pif(x)g(x)dxt1i=1g(pi)ti=1g(pi)g(p1)ptp1f(x)g(x)dxti=1g(pi)g(pt)t1i=1g(pi+1)t1i=1pi+1pif(x)g(x)dxt1i=1g(pi)ti=1g(pi)g(p1)ptp1f(x)g(x)dxti=1g(pi)g(pt)

因为gg是减函数,且恒大于00,所以g(p1)g(pt)g(p1)g(pt)是收敛的

于是

np1f(x)g(x)dxti=1g(i)lnn1np1f(x)g(x)dxti=1g(i)lnn1

两边同时求导得

f(x)lnxx11xf(x)x1xlnx=1lnx1xlnx1lnxf(x)lnxx11xf(x)x1xlnx=1lnx1xlnx1lnx

于是

π(x)xp11lntdtxlnxπ(x)xp11lntdtxlnx

最后一步的变换用洛必达就可以了

证毕

拉格朗日定理

f(x)是模p意义下的整系数n次多项式,f(x)pnf(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

f(x0)0f(x)(mod  p)f(xi)f(x0)0(mod  p)f(xi)f(x0)=(xix0)g(xi)(mod  p)  s.t.deg(g)n1xix0,g(xi)0(mod  p)f(x0)0f(x)(mod  p)f(xi)f(x0)0(mod  p)f(xi)f(x0)=(xix0)g(xi)(mod  p)  s.t.deg(g)n1xix0,g(xi)0(mod  p)

于是注意到g(x)g(x)x1,x2,x3...xnx1,x2,x3...xnnn 个根,矛盾

证毕

二次探测定理

pPp2x21(mod p)的解只有±1pPp2x21(mod p)±1

原方程可化为

(x1)(x+1)0(mod p)(x1)(x+1)0(mod p)

又有 pPpP

于是得证

小知识

向下取整小结论

nab=nabnab=nab

其中a,b,na,b,n均为正整数

证明如下:

nab=nab=nab+{na}b+[na mod b+{na}b]=nabnab=nab=nab+{na}b+[na mod b+{na}b]=nab

神秘小知识

d|nφ(d)=nd|nφ(d)=n

证明如下:

构造一个二元数列(h,k)(h,k)使得(h,k)=1kn1h<k(h,k)=1kn1h<k,特殊地,(1,1)(1,1)也被认为是合法的

构造另一个二元数列(i,n)(i,n)其中1in1in,

对于(h,k)(h,k)考虑其到(i,n)(i,n)的映射,

不难发现(h,k)(hnk,n)(h,k)(hnk,n)

h<kknhk<1hnk<n(i,n)=(hnk,n)h<kknhk<1hnk<n(i,n)=(hnk,n)

可以得到|(h,k)||(i,n)||(h,k)||(i,n)|

再考虑(i,n)(i,n)(h,k)(h,k)的映射

(i,n)(i(i,n),n(i,n))n(i,n)n(h,k)=(i(i,n),n(i,n))(i,n)(i(i,n),n(i,n))n(i,n)n(h,k)=(i(i,n),n(i,n))

可以发现|(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)|=dnφ(d)|(h,k)|=dnφ(d)

得证


ni=1ni=ni=1d(i)ni=1ni=ni=1d(i)

结合定义不难证得


d(ij)=xiyj[gcd(i,j)=1]d(ij)=xiyj[gcd(i,j)=1]


φ(ij)=φ(i)φ(j)gcd(i,j)φ(gcd(i,j))φ(ij)=φ(i)φ(j)gcd(i,j)φ(gcd(i,j))

μμ的最重要性质

Iμ=ϵIμ=ϵ

说人话就是μμII的反函数

证明如下

n=1,Iμ=μ(1)=1n=1,Iμ=μ(1)=1

n1n=paiin1n=paii

μμ的定义知,若num(m,p)>1μ(m)=0num(m,p)>1μ(m)=0

故而我们只考虑λ(n)i=1[num(m,pi)1]=1λ(n)i=1[num(m,pi)1]=1mm,下面的mm都满足这一性质

Si=λ(d)=iμ(d)Si=λ(d)=iμ(d)

Li=|Si|Li=|Si|

i1(mod2)Si=Lii1(mod2)Si=Li

i0(mod2)Si=Lii0(mod2)Si=Li

Si=(1)iLiSi=(1)iLi

就是说SiSi考虑有ii个质因子的mm的总贡献,LiLi表示这样的mm的个数,特殊地,我们有L0=S0=1L0=S0=1,也就是m=1m=1的贡献

由组合数的知识容易得到

Li=(λ(n)i)Li=(λ(n)i)

考虑用LiLi来表达IμIμ,简单带入可得

Iμ=d|nμ(d)=λ(n)i=1[num(m,pi)1]=1μ(m)=λ(n)i=0Si=λ(n)i=0(1)iLi=λ(n)i=0(1)i(λ(n)i)Iμ=d|nμ(d)=λ(n)i=1[num(m,pi)1]=1μ(m)=λ(n)i=0Si=λ(n)i=0(1)iLi=λ(n)i=0(1)i(λ(n)i)

考虑构造函数

f(x)=i=0(λ(n)i)xi=(1+x)λ(n)f(x)=i=0(λ(n)i)xi=(1+x)λ(n)

f(1)=λ(n)i=0(1)i(λ(n)i)=0f(1)=λ(n)i=0(1)i(λ(n)i)=0

所以有

n1,Iμ=0n1,Iμ=0

综上,得证

gcd(n,m)=1φ(nm)=φ(n)φ(m)gcd(n,m)=1φ(nm)=φ(n)φ(m)也就是说欧拉函数是积性函数,但不是完全积性函数

莫比乌斯反演

F(n)=d|nf(d)f(n)=d|nμ(d)·F(nd)F(n)=d|nf(d)f(n)=d|nμ(d)F(nd)

证明如下

F=fIF=fI

Fμ=fIμFμ=fIμ

Fμ=fFμ=f

得证

通常在使用时遵循如下方式

[...]ϵ(...)μ(...)[...]ϵ(...)μ(...)

交换求和号后暴力莫反即可

原根

定义

首先定义“阶”。$ a,,m,$的阶是

rminN  s.t.ar1(mod  m),gcd(a,m)=1rminN  s.t.ar1(mod  m),gcd(a,m)=1

a是m的原根  s.t.δm(a)=φ(m)am  s.t.δm(a)=φ(m)

其中,δm(a)δm(a)表示 aa 关于 mm 的原根

神秘定理

一个数 mm 的最小原根 gm14gm14

我不会证

一些引理

引理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

结合定义可得

(ab)lcm(δm(a),δm(b))1(mod  m),δm(ab)=δm(a)δm(b)δm(a)δm(b)|lcm(δm(a),δm(b))gcd(δm(a),δm(b))=1(ab)lcm(δm(a),δm(b))1(mod  m),δm(ab)=δm(a)δm(b)δm(a)δm(b)|lcm(δm(a),δm(b))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)

考虑如下变形

(ab)δm(ab)1(ab)δm(ab)δm(b)aδm(ab)(mod  m)δm(a)|δm(ab)(ab)δm(ab)1(ab)δm(ab)δm(b)aδm(ab)(mod  m)δm(a)|δm(ab)

同理可得

δm(b)|δm(ab)δm(b)|δm(ab)

又有

(ab)δm(a)δ(b)(ab)δm(ab)1(mod  m)δm(ab)|δm(a)δm(b)(ab)δm(a)δ(b)(ab)δm(ab)1(mod  m)δm(ab)|δm(a)δm(b)

已知gcd(δm(a),δm(b))=1gcd(δm(a),δm(b))=1

于是

δm(ab)|δm(a)δm(b)δm(a)δm(b)|δm(ab)δm(ab)|δm(a)δm(b)δm(a)δm(b)|δm(ab)

δm(a)δm(b)=δm(ab)δm(a)δm(b)=δm(ab)

证毕

引理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(a)gcd(δm(a),k)δm(ak)=δm(a)gcd(δm(a),k)

代入后易得右边是 δm(ak)δm(ak) 的倍数

只需证明左边也是右边的倍数

(ak)δm(ak)akδm(ak)1(mod  m)δm(a)|kδm(ak)(ak)δm(ak)akδm(ak)1(mod  m)δm(a)|kδm(ak)

gcd(δm(a),k)|kgcd(δm(a),k)|k

于是

δm(a)gcd(δm(a),k)|δm(ak)δm(a)gcd(δm(a),k)|δ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(a)=pαiiδm(b)=pβiiA=αiβipiB=βi<αipic=aAbBδm(a)=pαiiδm(b)=pβiiA=αiβipiB=βi<αipic=aAbB

可以发现符合要求,得证

存在原根的判断

结论:m=1,2,4,pa,2pam=1,2,4,pa,2pa 时才有原根

m=1,2,4m=1,2,4 时易得证

先考虑奇素数的情况

a=δp(n)=lcm(δp(1),δp(2)...δp(p1))(引理3)xa1(mod  p)至少有p1个根a=δp(n)=lcm(δp(1),δp(2)...δp(p1))(3)xa1(mod  p)p1

考虑拉格朗日定理

δp(n)p1δp(n)p1

又由费马小定理

δp(n)p1δp(n)p1

可得

δp(n)=p1=φ(p)δp(n)=p1=φ(p)

于是,nnpp的一个原根

ggpp 的一个原根,由归纳法可知

gφ(pa)=1+paCa  s.t.gCagφ(pa)=1+paCa  s.t.gCa

由欧拉定理得

δpa(g)φ(pa)=pa1(p1)δpa(g)φ(pa)=pa1(p1)

1ba,δpa(g)=pb1(p1)1ba,δpa(g)=pb1(p1)

考虑到

gδp(g)1(mod  pa)bagδp(g)1(mod  pa)ba

b=ab=a

δpa(g)=φ(pa)=φ(2pa)δpa(g)=φ(pa)=φ(2pa)

这样就得到了所有满足条件的情况,接着需要证明其他情况都无原根

p=2rp=2r 时,gg 只能为奇数

g=2k+1g2r2(2k+1)2r21+2r1k+2r1(2r21)k21+2r1k((2r21)k+1)1(mod  2r)δ2r(g)2r2<φ(2r)g=2k+1g2r2(2k+1)2r21+2r1k+2r1(2r21)k21+2r1k((2r21)k+1)1(mod  2r)δ2r(g)2r2<φ(2r)

无原根

否则,p=rt  s.t.gcd(r,t)=1p=rt  s.t.gcd(r,t)=1

gφ(r)1(mod  r)gφ(t)1(mod  t)gcd(r,t)=1gφ(r)φ(t)1g12φ(r)φ(t)(mod  p)δp(g)12φ(r)φ(t)<φ(p)gφ(r)1(mod  r)gφ(t)1(mod  t)gcd(r,t)=1gφ(r)φ(t)1g12φ(r)φ(t)(mod  p)δp(g)12φ(r)φ(t)<φ(p)

无原根

综上,只有m=1,2,4,pa,2pam=1,2,4,pa,2pa 时,mm 有原根

原根的求法

根据经验(我不会证),最小原根不是多项式级别的,所以可以暴力找最小的原根

显然,如果 ggmm 的原根,下面是充要条件

gφ(m)1(mod  m)gφ(m)/p1(mod  m)gφ(m)1(mod  m)gφ(m)/p≢1(mod  m)

复杂度大概是Θ(m14logmloglogm)Θ(m14logmloglogm)m14m14 是最小原根的上界,logmlogm 是快速幂,loglogmloglogm是枚举因子

然后有

gmgrm  s.t.gcd(r,φ(m))=1gmgrm  s.t.gcd(r,φ(m))=1

就可以求出所有原根了,证明以后有时间再补上吧,顺便我们还可以得到 mm 的原根数目为φ(φ(m))φ(φ(m))

积性函数的一些性质

通用性质

对于任意积性函数ff

n=paiin=paii

f(n)=f(paii)f(n)=f(paii)

有算数基本定理和积性函数的定义容易推出

对于任意完全积性函数ff

f(nk)=fk(n)f(nk)=fk(n)

若积性函数ff满足上式,则ff也是完全积性函数

d|nf(d)=λ(n)i=1num(n,pi)j=0f(pji)d|nf(d)=λ(n)i=1num(n,pi)j=0f(pji)

μμφφ的关联

由结论1得:

φI=idφI=id

考虑消去其中的II,利用结论2,两边同卷μμ

φIμ=idμφ=idμφ(n)=d|nμ(d)ndφ(n)n=d|nμ(d)dφIμ=idμφ=idμφ(n)=d|nμ(d)ndφ(n)n=d|nμ(d)d

一些自定义函数

φ2φ2

φ2(n,m)=ni=1mj=1[gcd(i,j)=1]=ni=1mj=1Ti,Tjμ(T)=min(n,m)T=1μ(T)ni=1mj=11=min(n,m)T=1μ(T)nTmTφ2(n,m)=ni=1mj=1[gcd(i,j)=1]=ni=1mj=1Ti,Tjμ(T)=min(n,m)T=1μ(T)ni=1mj=11=min(n,m)T=1μ(T)nTmT

ϕ2

ϕ2(n,m)=ni=1mj=1ij[gcd(i,j)=1]=ni=1imj=1jTi,Tjμ(T)=min(n,m)T=1μ(T)ni=1imj=1j=min(n,m)T=1μ(T)S(nT)S(mT)

一点补充

(μid)id=ϵ

暴力展开即可得证,读者自证不难


算法

卢卡斯定理&拓展卢卡斯定理

算法

所以为什么叫定理啊

不妨直接说 exLucas 定理,可以用来解决

Cmnmodp

其中,p 可以是任意正整数

不妨直接展开

Cmn=n!m!(nm)!

如果 p 的质因子足够大,就可以直接求 m!(nm)! 的逆元

如果 p 比较小,我们考虑

p=αi>0pαii

注意到

gcd(pαii,pαjj)=1

原问题就转化为了求

Cmnmodpks.t.pP,kN

之后有

{Cmna1(mod  pα11)Cmna2(mod  pα22)Cmna3(mod  pα33)...Cmnar(mod  pαrr)

使用中国剩余定理求解即可得到 Cnmmodp,复杂度为 Θ(λ(p))

发现,只要 Cmn 中没有 p 这个质因子,就可以直接求逆元

这时,一个朴素的思想是把其中的 p 都提出来,最后再乘回去,即

Cmn=n!m!(nm)!=n!pc1m!pc2(nm)!pc3pc1c2c3

只需要处理 c1c2c30 的情况

转化为处理如下情形

n!pαmodpks.t.gcd(n!pα,p)=1

发现并没有很好的方法,于是考虑一次只除去一个 p ,也就是说将 1n 的所有 p 的倍数都除去一个 p

注意到这样的数有 n!p

除去 p 之后,有

1inpii=pnpnp!

显然可以递归直到 n!p<p

再观察 n! 中不含 p 的项,有

pkpiipkpi(i+qpk)(mod  pk)

于是可以发现循环节,最后一节可以不完整,但长度不超过 pk,所以这一步的复杂度是Θ(pk+lognpk)

因为 p 已经被提出来了,所以最后的结果就是

n!pαnp!(pkpii)npkni=pknpk+1i(mod  pk)

注意其中每一部分计算时都需要忽视 p (因为需要提出来放在外面处理)

这样就解决了问题,有一个简洁的式子,但是我忘了

复杂度

p 的质因子个数为 λ(p),又有 Θ(λ(p))=Θ(loglogp)

接着分析计算 n!pαmodpk 的复杂度

设其复杂度为 Θ(fp(n)),则

Θ(fp(n))=Θ(fp(np)+pk+lognpk)

至多递归 logpn 次,于是有

Θ(fp(n))=Θ(pklogpn+G)G=pk+ini=0lognpk+i=(logpnk)lognpkilogpnki=0logpi

F=logpnk

G=FlognpkF2logpΘ(G)=Θ(logn)

再全部加起来

Θ(exLucas)=λ(p)i=1Θ(fpi(n))=Θ(lognloglogp)+c

左边不太可能是瓶颈,只考虑右边

Θ(lognλ(p)i=1pkilogpi)Θ(plogn)

这样放缩显然过于粗略(但确实是界,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

axb(mod p),求 x

显然最小的答案不会超过 p(只有 p 种余数,之后一定会循环),于是可以做到 O(p)

考虑根号分治

x=kp+l(k<p)akpbal(mod p)

于是可以枚举 k,把对应的值插到一个hash表里,然后枚举 l,判断hash表中有没有对应的值即可

于是做到 O(p)(严格来说是 O(φ(p))

如果 gcd(a,p)>1,那么不能直接采用上面的方法,因为求不出逆元

此时需要exBSGS

显然,b=1x=0

考虑其他情况,设

g=gcd(a,p)a=agp=pg

axb(mod p)(ag)xb(mod pg)(ag)x+kpg=bg(aax1+kp)=b

有解的必要条件是 bgZ

最后一个式子变换得

aax1bg(mod p)

gcd(a,p)=1

a 在模 p 的意义下一定存在逆元,然后用 BSGS 就可以了

另一种方便的方法是直接做 BSGS,枚举 l 即可,然后代回验证即可,不过细节挺多的

Miller-Rabin

由费马小定理

ap11(mod p)pP

发现这样的运算是 O(log p) 的,但符合上式的 p 也不一定是素数,于是用二次探测定理二次验证

二次验证时注意到 p1 一定是偶数,于是取 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,使得

h=fg

S(n)=ni=1f(i),Sh(n)=ni=1h(i)

按狄利克雷卷积的定义展开

Sh(n)=ni=1d|ig(d)f(id)

考虑用Sh表示S,于是考虑交换求和符号,使得gf分离:

ni=1d|ig(d)f(id)=nd=1g(d)ndi=1f(i)=nd=1g(d)S(nd)

发现n1=n,所以我们把右边第一项提出来:

Sh(n)=g(1)S(n)+nd=2g(d)S(nd)

整理得

S(n)=Sh(n)nd=2g(d)S(nd)g(1)

由于构造的Sh(n)是容易求得的,即可以在低于线性的时间内求出(实际上很多时候都是Θ(1)的),

g(1)也显然可以在Θ(1)的时间里求出,所以只需要在低于线性的时间里求g(d)S(nd)的除去第一项的前缀和

考虑使用整除分块,则转化为求g在不超过2n段上的和,前缀和即可,

对于S([nd])项,我们直接递归,暴力地预处理出n23个前缀和即可

复杂度分析

下文考虑中均认为求f,g的前缀和是Θ(1)

运用杜教筛,我们把求S(n)转化为了求所有的S(nd),本质上只有2n种取值,忽略常数后,我们设杜教筛的复杂度为T(n),可得方程

T(n)=n+[n]i=2(T(i)+T(ni))

含义为,求前n项前缀和的复杂度是整除分块的复杂度+处理出每个S(nd)S(d)的复杂度

这里我们默认了n超出了预处理的前缀和,但nd显然可能是被预处理过的值,所以需要考虑预处理,设预处理了k(kn)个,那么总的复杂度为:

k+T(n)=k+n+ni=2(T(i)+T(ni))

注意到所有T(i)都已经被预处理了,可以Θ(1)得到,复杂度为Θ(n)

接着考虑T(ni)的处理,考虑继续展开,注意此时不需要再预处理了:

k+T(n)=k+n+ni=2T(ni)=k+n+nki=2nk

上式最后的含义为一个大于k的值迭代到k以内的迭代次数

令总复杂度为D=k+T(n),选k为主元,考虑其极值,得

D=1n2k32=0

2k32=n

k=(n2)23

最终的复杂度为Θ(n23).

min25筛

函数F(x)的前缀和,F(pk)=pol(pk)pol是多项式函数

对于部分函数,我们容易构造出杜教筛解决,但如果辅助函数不好构造或者干脆不是积性函数,就不能杜教筛了,此时,我们可以考虑min25筛(当然min_25筛对于函数的性质还是有要求的,一般不是积性函数的话,合数部分贡献的计算很好处理,下文只考虑是积性函数的情况)

计算方法

对于一个多项式,我们可以把每一项的贡献分开考虑,而常数显然可以最后再乘,所以我们只需要考虑F(p)=pk的情况

考虑一个更简单的问题

pnpk

不能线筛的话似乎毫无办法,但这里可以考虑dp,记

g(n,x)=iPlpf(i)>pxik

也就是说g(n,x)表示区间[1,n]中质数或最小质因子比第x大质数大的数的k次方的贡献

怎么转移呢?

我们考虑容斥,显然答案会减小,失去贡献的是最小质因子恰好为pj的合数,又发现pk这种幂函数是完全积性的

所以选定一个pj除掉,再补上多减的,可以得到转移方程:

g(n,j)=g(n,j1)pkj(g(npj,j1)g(pj1,j1))

答案就是g(n,π(n)),可是,这还是线性的呀?

但是,我们发现

1iniPlpf(i)n

于是最后的g(pj1,j1)就可以Θ(n)线筛预处理了

至于前面的部分,之后再说明

回到原问题,我们把贡献拆成质数和合数两部分,然后枚举合数的lpf及其次数

SF(n)=π(n)i=1pki+iPF(i)=π(n)i=1pki+penpek(1inpelpf(i)>pF(i))

S(i,j)表示前i个数中的质数或最小质因子不小于pj的合数的贡献

S(i,j)=kPlpf(k)pjF(k)=g(i,π(i))g(j1,π(j1))+kjp2kipe+1kie=1(F(pek)S(ipek,k+1)+F(pe+1k))

不难发现,边界条件为

S(1,j)=S(i,j)(i<pj)=0

最终答案就是

SF(n)=S(n,1)+F(1)

这时,我们发现我们只需要求S(n,1),于是我们求的g形如

g(n,π(n)),g(na1,π(...)),g(na1a2,π(...))=g(na1a2),π(...))

可以发现只需要求Θ(n)g的值就可以了

复杂度分析

我们发现min_25筛干了这些事:求Θ(n)g的值,求Θ(n)个关键点的g值,求S(n,1)

第一个可以线筛,所以瓶颈在后两个上

由素数定理,质数个数约为nlnn

联想埃筛,可以发现一个数x被筛的次数是Θ(xlogx)(此处不区分logln的区别,主要是为了和其他算法复杂度写法又一致)

于是可以得到方程:

T(n)=nx=1(Θ(xlogx)+Θ(nxlognx))

由离散到连续变换:

T(n)=n0(xlogx+nxlognx)dx=n0xlogxdx+n0nxlognxdx

可以证明左边总是不大于右边,于是

T(n)=Θ(n0nxlnnxdx)=Θ(n0nxlnnxdx)=Θ(n0nxlnnlnxdx)=Θ(nn0dxx(lnnlnx))=Θ(n(n14lnnlnnn0xdxlnnlnx))Θ(n34logn)

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,pPpn,p2n

说人话就是所有质因子次数不小于2的数

还是求积性函数的前缀和,构造函数g,g(p)=f(p),并使得g的前缀和容易求出

f=gh

f(p)=g(1)h(p)+g(p)h(1)=h(p)+g(p)h(p)=0

考虑展开卷积,得

S(n)=ni=1f(i)=ni=1dig(d)h(id)

考虑g的前缀和是容易求出的,交换求和号

S(n)=ni=1h(i)nidg(d)=ni=1h(i)Sg(ni)

这依然是Θ(n)的,但因为h是积性函数并且h(p)=0,所以h(n)0,nPowerful Number

考虑Powerful Number的数量,以下的qPowerful Number

由定义知

q=λ(q)i=1pxiii,xi2

上文的pi仅表示q的质因子

不难发现

i,xi=2ai+3bi(ai,bi0)

那么

q=λ(q)i=1(paii)2(pbii)3q=A2B3

power(n)=[nPowerful Number],那么powerful number的数目就是power()的前缀和

考虑枚举A,计算对于的贡献,为了不算重,应该只枚举1n,那么

Spower(n)=ni=1power(i)=nx=1ni=1[3ix2Z]n13nx2dx=32(nn13)=Θ(n)

那么就只需要求出[1,n]中的所有powerful number,然后就可以Θ(Fn)求出f的前缀和,其中F是求g的前缀和的复杂度

接下来介绍一个小技巧:

观察f=gh,发现fg1=ϵh=h,其中g1g=ϵ

所以只要构造出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

后记

咕咕咕,咕~

posted @   嘉年华_efX  阅读(8041)  评论(1编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示

目录导航