數論筆記
前言
本文主要基本地介紹知識點,通常附有證明,可能並不太系統,順序可(一)能(定)也有一些問題(記得看目錄)
本文主要需要的前置芝士:同余,\(\,gcd,lcm\,\),乘法逆元,線性篩,剩余系,簡化剩余系,導數
本文主要講述了(不完全按順序):(擴展)歐拉定理,費馬小定理,數論函數的定義,常用積性函數及其變換,狄利克雷卷積,莫比烏斯反演,杜教篩,min_25篩,Powerful Number篩
有時間將補充:Pollard Rho,Pohlig-Hellman
本文篇幅較長,請耐心閱讀,如有錯誤,歡迎指出
定義
一些規定
1.如無特殊標記,\(f_k(n)=f^k(n)\)
2.如無特殊說明,\(num(n,p)=max(k \in {p^k|n})\)
3.\([x]\)按照語境通常是艾弗森符號的含義或表示閉區間,有時也作向下取整(我盡量用最規范的),\(\{x\}\)表示取小數部分,有時也作集合使用
4.\(\,p\,\)表示容易質數,\(\,P\,\)表示質數集,如無特殊說明,\(\,p_i\,\)表示第\(\,i\,\)大的質數
5.\(\,lpf(x)\,\)表示\(\,x\,\)的最小質因子
6.\(\delta_m(a)\,\)表示\(\,a\,\)關於\(\,m\,\)的階
7.使用勒讓德符號,\((n|p)\equiv n^{\frac{p-1}2}(\mod p)\)
許多數論函數
素數個數函數
歐拉函數\(\,\varphi(x)\):[1,n] 中與n互質的數的個數
劉維爾函數(不是積性函數)
莫比烏斯函數
約數個數函數
元函數
恆等函數
單位函數
約數和函數
一維前綴和
自定義的幾個常用函數:
證明放在后面
積性函數:
完全積性函數:
狄利克雷卷積
定義
設\(f,g\)為積性函數
則\(f\,\ast\,g=\sum_{d|n}f(d)g(\frac{n}{d})\)
狄利克雷卷積的一些性質
\(f\,\ast\,\epsilon\,=\,f\)
\(f\,\ast\,g=g\,\ast\,f\)
\(f\,\ast\,(g\,\ast\,h)=(f\,\ast\,g)\ast\,h\)
\((f+g)\,\ast\,h=\,f\,\ast\,h\,+\,g\,\ast\,h\)
兩個積性函數卷完之后還是積性函數
上述結論均不難證明,在此略過
定理
歐拉定理&擴展歐拉定理
特殊地,\(gcd(a,p)=1\,\)時,有\(\,\,a^k\equiv\,a^{k\mod\varphi(p)}(mod\,\,p)\)
注意,使用拓展歐拉定理時應當判斷\(\,k\,\)與\(\varphi (p)\)的大小
歐拉定理的證明
考慮證明簡單一點的情況:
不難發現這和特殊情況是等價的(其實就是歐拉定理)
考慮\(\,p\,\)的簡化剩余系(似乎也有叫縮系的)\(x_1,x_2\cdots x_{\varphi(p)}\)
構造數列\(\,y_i=ax_i\)
引理
\(\,y\,\)也是\(\,p\,\)的簡化剩余系
引理證明
發現簡化剩余系有兩個主要性質:與原數互質,%\(p\)后兩兩不等
考慮分別證明
證明與原數互質
由簡化剩余系的定義
再證明%\(p\)后兩兩不等
有
結合兩條性質不難證明引理
有了引理我們不難發現,\(\,y\,\)和\(\,x\,\)只是在次序上不同
所以
得證
拓展歐拉定理的證明
同余的基本性質告訴我們
拓展歐拉定理寫成如下形式
令
發現只需要證明對於每一個\(\,p_i^{q_i}\,\)拓展歐拉定理成立,對於\(\,m\,\),擴展歐拉定理就也成立
若
那么就是歐拉定理的情形,只需考慮\(\,gcd(a,p_i^{q_i})\ne1\,\)的情況
即\(\,p_i\,\)是質數,所以\(\,a=np_i\)
引理
\(\varphi(p^k)\geq k\)
考慮\(k=0,1\)時帶入驗證即可
對於\(k>1\)的情況,兩邊同時求導,即左邊的導數為\(l^\prime\),右邊的導數為\(r^\prime\)
得到
所以不等式在\(k>1\)時恆成立
引理得證
有
所以
於是\(\,gcd(a,p_i^{q_i})\ne1\,\)的情形也就得證了
證畢
費馬小定理
其實就是歐拉函數的性質在歐拉定理中的應用:
證明略過
順帶一提,並不是所有合數都不符合費馬小定理,但是費馬小定理可以用來初步判斷素數
中國剩余定理&擴展中國剩余定理
描述了這樣一個構造方法
代入即可發現這樣的 \(x\) 是一個解,於是就可以求出 \(x\mod M\) 了,可以用來求解大數取模
擴展中國定理(EXCRT)中則不需要 \(\forall i,j\ gcd(m_i,m_j)=1\) 這一條件
此時沒有人類智慧的構造方法了
觀察性質,記 \(LCM=lcm(m_1,m_2\dots m_n)\) 最后的答案一定是
於是考慮合並方程,直至最后剩下一個作為通解
已知
則
注意到 \(m_1,m_2,r_1,r_2\) 都已知,運用 \(ex\ gcd\) 即可求出一組 \(k_1,k_2\),代回即可求出通解
同理消去方程即可解出通解,中間任何一次合並的無解都會導致最終無解
素數定理
\(\pi(x)\sim\frac{x}{\ln x}\)
不太重要,不過證明一些復雜度時需要用到,這里給出簡略的初等證明(不用\(\,\zeta\,\)函數和復變換)
考慮從離散到連續的變化,又可以寫作
注意到:
\(p\mid n!\,\)當且僅當\(\,p\leq n\,\)
記
那么有
可以注意到
這似乎並不可做,但可以放縮
取對數得
用斯特林公式(或許以后會在多項式的文章里證明,不過不會也沒什么關系)擬合\(\,n!\,\):
於是,我們有
記
不難發現\(\,x\geq2\,\)時,\(\frac{\\\ln x}{x-1}\)是減函數
於是
設
則
那么
有
於是
對\(\,i\,\)進行求和
因為\(\,g\,\)是減函數,且恆大於\(\,0\),所以\(\,g(p_1)-g(p_t)\,\)是收斂的
於是
兩邊同時求導得
於是
最后一步的變換用洛必達就可以了
證畢
拉格朗日定理
\(\text{f(x)是模p意義下的整系數n次多項式,}\)\(f(x)\equiv 0(mod\ \ p)\text{至多有n個不同解}\)
考慮歸納法
\(n=0\,\)時顯然成立
若\(f(x)\equiv 0(mod\ \ p)\)有\(\,n+1\,\)個不同解,依次記為\(\,x_0,x_1,x_2...x_n\)
有
於是注意到\(\,g(x)\,\)有\(\,x_1,x_2,x_3...x_n\,\)共 \(n\) 個根,矛盾
證畢
二次探測定理
\(p\in P\and p\neq2\Rightarrow x^2\equiv 1(mod\ p)\text{的解只有}\pm1\)
原方程可化為
又有 \(p\in P\)
於是得證
小知識
向下取整小結論
其中\(\,a,b,n\,\)均為正整數
證明如下:
神秘小知識
證明如下:
構造一個二元數列\(\,(h,k)\,\)使得\(\,(h,k)=1\,\and\,k\mid n\,\and\,1\leq h<k\),特殊地,\((1,1)\)也被認為是合法的
構造另一個二元數列\(\,(i,n)\,\)其中\(\,1\leq i\leq n\),
對於\(\,(h,k)\,\)考慮其到\(\,(i,n)\,\)的映射,
不難發現\(\,(h,k)\rightarrow (\frac{hn}{k},n)\)
可以得到\(\,|(h,k)|\leq|(i,n)|\,\)
再考慮\(\,(i,n)\,\)到\(\,(h,k)\,\)的映射
可以發現\(\,|(i,n)|\leq|(h,k)|\,\)
放縮得\(\,|(i,n)|=|(h,k)|\,\)
顯然有\(\,|(i,n)|=n\,\),所以\(\,|(h,k)|=n\,\)
結合定義知\(k=d\Rightarrow (h,k)=\varphi(d)\),即\(|(h,k)|=\sum_{d\mid n}\varphi(d)\)
得證
結合定義不難證得
\(\,\mu\,\)的最重要性質
說人話就是\(\,\mu\,\)是\(\,I\,\)的反函數
證明如下
若\(n\neq1\rightarrow n=\prod p_i^{a_i}\)
由\(\mu\,\)的定義知,若\(\,\,\exists\,num(m,p)>1\rightarrow \mu(m)=0\)
故而我們只考慮\(\,\,\prod\limits_{i=1}^{\lambda(n)}[num(m,p_i)\leq 1]=1\,\)的\(\,m\,\),下面的\(\,m\,\)都滿足這一性質
設
就是說\(\,S_i\,\)考慮有\(\,i\,\)個質因子的\(\,m\,\)的總貢獻,\(\,L_i\,\)表示這樣的\(\,m\,\)的個數,特殊地,我們有\(L_0=S_0=1\),也就是\(\,m=1\,\)的貢獻
由組合數的知識容易得到
考慮用\(\,L_i\,\)來表達\(\,I\,\ast\,\mu\,\),簡單帶入可得
考慮構造函數
所以有
綜上,得證
\(gcd(n,m)=1\rightarrow\varphi(nm)=\varphi(n)\varphi(m)\quad\)也就是說歐拉函數是積性函數,但不是完全積性函數
莫比烏斯反演
證明如下
得證
通常在使用時遵循如下方式
交換求和號后暴力莫反即可
原根
定義
首先定義“階”。$ a,\(關於\),m,$的階是
其中,\(\delta_m(a)\)表示 \(a\) 關於 \(m\) 的原根
神秘定理
一個數 \(m\) 的最小原根 \(g\leq m^\frac14\)
我不會證
一些引理
引理1
\(\gcd(a,m)=\gcd(b,m)=1\text{時}\delta_m(a)\delta_m(b)=\delta_m(ab)\Leftrightarrow \gcd(\delta_m(a),\delta_m(b))=1\)
先證,\(\delta_m(a)\delta_m(b)=\delta_m(ab)\Rightarrow \gcd(\delta_m(a),\delta_m(b))=1\)
結合定義可得
再證,\(\gcd(\delta_m(a),\delta_m(b))=1\Rightarrow \delta_m(a)\delta_m(b)=\delta_m(ab)\)
考慮如下變形
同理可得
又有
已知\(\,\gcd(\delta_m(a),\delta_m(b))=1\)
於是
即
證畢
引理2
\(\gcd(a,m)=1,\delta_m(a^k)=\frac{\delta_m(a)}{\gcd(\delta_m(a),k)}\)
給個式子的大寫
代入后易得右邊是 \(\delta_m(a^k)\) 的倍數
只需證明左邊也是右邊的倍數
有
於是
得證
引理3
\(\forall \gcd(a,m)=\gcd(b,m)=1,\exists c\ s.t. \delta_m(c)=lcm(\delta_m(a),\delta_m(b))\)
考慮如下構造(反正我考慮不到)
可以發現符合要求,得證
存在原根的判斷
結論:\(m=1,2,4,p^a,2p^a\) 時才有原根
\(m=1,2,4\) 時易得證
先考慮奇素數的情況
考慮拉格朗日定理
又由費馬小定理
可得
於是,\(n\,\)為\(\,p\,\)的一個原根
設 \(g\) 為 \(p\) 的一個原根,由歸納法可知
由歐拉定理得
設 \(1\leq b\leq a,\delta_{p^a}(g)=p^{b-1}(p-1)\)
考慮到
這樣就得到了所有滿足條件的情況,接着需要證明其他情況都無原根
\(p=2^r\) 時,\(g\) 只能為奇數
無原根
否則,\(p=rt\ \ s.t.\gcd(r,t)=1\)
無原根
綜上,只有\(m=1,2,4,p^a,2p^a\) 時,\(m\) 有原根
原根的求法
根據經驗(我不會證),最小原根不是多項式級別的,所以可以暴力找最小的原根
顯然,如果 \(g\) 是 \(m\) 的原根,下面是充要條件
復雜度大概是\(\Theta(m^\frac14\log m\log\log m)\),\(m^\frac14\) 是最小原根的上界,\(\log m\) 是快速冪,\(\log\log m\)是枚舉因子
然后有
就可以求出所有原根了,證明以后有時間再補上吧,順便我們還可以得到 \(m\) 的原根數目為\(\varphi(\varphi(m))\)
積性函數的一些性質
通用性質
對於任意積性函數\(f\,\)有
有算數基本定理和積性函數的定義容易推出
對於任意完全積性函數\(f\,\)有
若積性函數\(f\,\)滿足上式,則\(f\,\)也是完全積性函數
\(\,\mu\,\)與\(\,\varphi\,\)的關聯
由結論1得:
考慮消去其中的\(\,I\,\),利用結論2,兩邊同卷\(\,\mu\):
一些自定義函數
\(\varphi^2\)
\(\phi^2\)
一點補充
暴力展開即可得證,讀者自證不難
算法
盧卡斯定理&拓展盧卡斯定理
算法
所以為什么叫定理啊
不妨直接說 \(exLucas\) 定理,可以用來解決
\(C_n^m\mod p\)
其中,\(p\) 可以是任意正整數
不妨直接展開
如果 \(p\) 的質因子足夠大,就可以直接求 \(m!(n-m)!\) 的逆元
如果 \(p\) 比較小,我們考慮
注意到
原問題就轉化為了求
之后有
使用中國剩余定理求解即可得到 \(C_m^n\mod p\),復雜度為 \(\Theta(\lambda(p))\)
發現,只要 \(C_n^m\) 中沒有 \(p\) 這個質因子,就可以直接求逆元
這時,一個朴素的思想是把其中的 \(p\) 都提出來,最后再乘回去,即
只需要處理 \(c_1-c_2-c_3\ne0\) 的情況
轉化為處理如下情形
發現並沒有很好的方法,於是考慮一次只除去一個 \(p\) ,也就是說將 \(1\sim n\) 的所有 \(p\) 的倍數都除去一個 \(p\)
注意到這樣的數有 \(\lfloor\frac{n!}{p}\rfloor\) 個
除去 \(p\) 之后,有
顯然可以遞歸直到 \(\lfloor\frac{n!}{p}\rfloor< p\)
再觀察 \(n!\) 中不含 \(p\) 的項,有
於是可以發現循環節,最后一節可以不完整,但長度不超過 \(p^k\),所以這一步的復雜度是\(\Theta(p^k+\log\frac{n}{p^k})\)
因為 \(p\) 已經被提出來了,所以最后的結果就是
注意其中每一部分計算時都需要忽視 \(p\) (因為需要提出來放在外面處理)
這樣就解決了問題,有一個簡潔的式子,但是我忘了
復雜度
\(p\) 的質因子個數為 \(\lambda(p)\),又有 \(\Theta(\lambda(p))=\Theta(\log\log p)\)
接着分析計算 \(\frac{n!}{p^\alpha}\mod p^k\) 的復雜度
設其復雜度為 \(\Theta(f_p(n))\),則
至多遞歸 \(\log_pn\) 次,於是有
記
則
再全部加起來
左邊不太可能是瓶頸,只考慮右邊
這樣放縮顯然過於粗略(但確實是界,\(p=2^\alpha\) 時取到),一般估計時可以采用作為近似的下界 \(\Theta(p^{\frac1{\log\log\log p}}\frac{\log\log p}{\log p}\log n)\)
真的下界可能是 \(\Theta(p^{\frac1{\log\log p}}\frac{\log\log p}{\log p}\log n)\)
是假設有 \(\log\log p\) 個等大的因數得出的
代碼
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
\(a^x\equiv b(mod\ p)\),求 \(x\)
顯然最小的答案不會超過 \(p\)(只有 \(p\) 種余數,之后一定會循環),於是可以做到 \(O(p)\)
考慮根號分治
於是可以枚舉 \(k\),把對應的值插到一個hash表里,然后枚舉 \(l\),判斷hash表中有沒有對應的值即可
於是做到 \(O(\sqrt p)\)(嚴格來說是 \(O(\sqrt{\varphi(p)})\))
如果 \(gcd(a,p)>1\),那么不能直接采用上面的方法,因為求不出逆元
此時需要exBSGS
顯然,\(b=1\Rightarrow x=0\)
考慮其他情況,設
則
有解的必要條件是 \(\frac bg\in Z\)
最后一個式子變換得
有
故 \(a^\prime\) 在模 \(p^\prime\) 的意義下一定存在逆元,然后用 BSGS 就可以了
另一種方便的方法是直接做 BSGS,枚舉 \(-l\) 即可,然后代回驗證即可,不過細節挺多的
Miller-Rabin
由費馬小定理
發現這樣的運算是 \(O(log\ p)\) 的,但符合上式的 \(p\) 也不一定是素數,於是用二次探測定理二次驗證
二次驗證時注意到 \(p-1\) 一定是偶數,於是取 \(\frac p2\) 繼續檢測,結果只能是 \(\pm1\)
這樣做,一次的錯誤概率是 \(\frac14\),可以接受
\(\text{如果廣義黎曼猜想成立,只需取前}\lfloor2(log\ n)^2\rfloor\text{個數進行費馬小定理的檢驗即可確定性地判素}\)
雖然我們不難知道廣義黎曼猜想是否成立,但在 \(2^{78}\) 的范圍內只需要取 \(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\,\),使得
按狄利克雷卷積的定義展開
考慮用\(\,S_h\,\)表示\(\,S\),於是考慮交換求和符號,使得\(\,g\,\)和\(\,f\,\)分離:
發現\(\,\frac{n}{1}=n\,\),所以我們把右邊第一項提出來:
整理得
由於構造的\(S_h(n)\)是容易求得的,即可以在低於線性的時間內求出(實際上很多時候都是\(\,\Theta(1)\,\)的),
\(\,g(1)\,\)也顯然可以在\(\,\Theta(1)\,\)的時間里求出,所以只需要在低於線性的時間里求\(\,g(d)S(\frac{n}{d})\,\)的除去第一項的前綴和
考慮使用整除分塊,則轉化為求\(\,g\,\)在不超過\(\,2\sqrt n\,\)段上的和,前綴和即可,
對於\(\,S([\frac{n}{d}])\,\)項,我們直接遞歸,暴力地預處理出\(n^{\frac{2}{3}}\)個前綴和即可
復雜度分析
下文考慮中均認為求\(\,f,g\,\)的前綴和是\(\,\Theta(1)\,\)的
運用杜教篩,我們把求\(\,S(n)\,\)轉化為了求所有的\(\,S(\frac{n}{d})\,\),本質上只有\(\,2\sqrt n\,\)種取值,忽略常數后,我們設杜教篩的復雜度為\(\,T(n)\,\),可得方程
含義為,求前n項前綴和的復雜度是\(\,\)整除分塊的復雜度+處理出每個\(\,S(\frac{n}{d})\,\)和\(\,S(d)\,\)的復雜度
這里我們默認了\(\,n\,\)超出了預處理的前綴和,但\(\,\frac{n}{d}\,\)顯然可能是被預處理過的值,所以需要考慮預處理,設預處理了\(\,k\,(k\geq\sqrt n)\,\)個,那么總的復雜度為:
注意到所有\(\,T(i)\,\)都已經被預處理了,可以\(\,\Theta(1)\,\)得到,復雜度為\(\,\Theta(\sqrt n)\,\),
接着考慮\(\,T(\frac{n}{i})\,\)的處理,考慮繼續展開,注意此時不需要再預處理了:
上式最后的\(\,\sum\,\)含義為一個大於\(\,k\,\)的值迭代到\(\,k\,\)以內的迭代次數
令總復雜度為\(\,D=k+T(n)\,\),選\(\,k\,\)為主元,考慮其極值,得
最終的復雜度為\(\,\Theta(n^\frac{2}{3})\).
min25篩
求函數\(F(x)\)的前綴和,\(F(p^k)=pol(p^k)\),\(\,pol\,\)是多項式函數
對於部分函數,我們容易構造出杜教篩解決,但如果輔助函數不好構造或者干脆不是積性函數,就不能杜教篩了,此時,我們可以考慮min25篩(當然min_25篩對於函數的性質還是有要求的,一般不是積性函數的話,合數部分貢獻的計算很好處理,下文只考慮是積性函數的情況)
計算方法
對於一個多項式,我們可以把每一項的貢獻分開考慮,而常數顯然可以最后再乘,所以我們只需要考慮\(F(p)=p^k\)的情況
考慮一個更簡單的問題
求\(\sum\limits_{p\leq n}p^k\)
不能線篩的話似乎毫無辦法,但這里可以考慮dp,記
也就是說\(\,g(n,x)\,\)表示區間\(\,[1,n]\,\)中質數或最小質因子比第\(\,x\,\)大質數大的數的\(\,k\,\)次方的貢獻
怎么轉移呢?
我們考慮容斥,顯然答案會減小,失去貢獻的是最小質因子恰好為\(\,p_j\,\)的合數,又發現\(p^k\)這種冪函數是完全積性的
所以選定一個\(p_j\)除掉,再補上多減的,可以得到轉移方程:
答案就是\(\,g(n,\pi(n))\,\),可是,這還是線性的呀?
但是,我們發現
於是最后的\(g(p_{j-1},j-1)\)就可以\(\Theta(\sqrt n)\)線篩預處理了
至於前面的部分,之后再說明
回到原問題,我們把貢獻拆成質數和合數兩部分,然后枚舉合數的\(\,lpf\,\)及其次數
設\(\,S(i,j)\,\)表示前\(\,i\,\)個數中的質數或最小質因子不小於\(\,p_j\,\)的合數的貢獻
即
不難發現,邊界條件為
最終答案就是
這時,我們發現我們只需要求\(\,S(n,1)\),於是我們求的\(\,g\,\)形如
可以發現只需要求\(\,\Theta(\sqrt n)\,\)個\(\,g\,\)的值就可以了
復雜度分析
我們發現min_25篩干了這些事:求\(\Theta(\sqrt n)\,\)個\(\,g\,\)的值,求\(\,\Theta(\sqrt n)\,\)個關鍵點的\(\,g\,\)值,求\(\,S(n,1)\,\)
第一個可以線篩,所以瓶頸在后兩個上
由素數定理,質數個數約為\(\,\frac{n}{\\\ln n}\,\)
聯想埃篩,可以發現一個數\(\,x\,\)被篩的次數是\(\,\Theta(\frac{\\\sqrt x}{\log \sqrt 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為\(\forall n,\forall p\in P\,\and p\mid n,p^2\mid n\)
說人話就是所有質因子次數不小於2的數
還是求積性函數的前綴和,構造函數\(g,g(p)=f(p)\),並使得\(\,g\,\)的前綴和容易求出
令
有
考慮展開卷積,得
考慮\(\,g\,\)的前綴和是容易求出的,交換求和號
這依然是\(\,\Theta(n)\,\)的,但因為\(\,h\,\)是積性函數並且\(\,h(p)=0\),所以\(\,\forall h(n)\neq 0,n\in Powerful\ Number\)
考慮\(\,Powerful\ Number\,\)的數量,以下的\(\,q\,\)是\(Powerful\ Number\)
由定義知
上文的\(\,p_i\,\)僅表示\(\,q\,\)的質因子
不難發現
那么
記\(\,power(n)=[n\in Powerful\ Number]\,\),那么powerful number的數目就是\(\,power()\,\)的前綴和
考慮枚舉\(\,A\),計算對於的貢獻,為了不算重,應該只枚舉\(1-\sqrt n\),那么
那么就只需要求出\([1,n]\)中的所有powerful number,然后就可以\(\Theta(F\sqrt n)\)求出\(\,f\,\)的前綴和,其中\(\,F\,\)是求\(\,g\,\)的前綴和的復雜度
接下來介紹一個小技巧:
觀察\(f=g\,\ast\,h\),發現\(f\,\ast\,g^{-1}=\epsilon\,\ast\,h=h\),其中\(g^{-1}\,\ast\,g=\epsilon\)
所以只要構造出\(\,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
后記
咕咕咕,咕~