杜教篩
(彷佛有不少人在催個人杜教篩呢......)html
前言
- 話說,我是否是在本身的莫比烏斯反演中挖了許多杜教篩的坑啊......
- 本文完整的總結介紹杜教篩,也算是將莫比烏斯反演中的坑所有填滿吧!
- 真誠地但願來閱讀這篇學習筆記的每個人,仔仔細細的看完每一段。
- 我相信,只要認真的看完整篇文章並跟着一塊兒思考的讀者,必定可以有所收穫!
- 若是您以前不會杜教篩,那麼我但願這篇文章可以做爲您學習杜教篩路上的有力援助,幫助您真正的瞭解與掌握杜教篩!
- 若是您以前早已熟知杜教篩或只有些模糊的印象,相信您必定也能有所收穫!
- (PS:本文較長,請耐心閱讀 ovo)
在OI中的意義
- 其實,對於通常的數論題,線性篩已經很是的優秀了。
- 可是就是有那些\(duliu\)出題人,硬是要把數據出到\(1e10\)之類的,就看你會不會杜教篩,min_25篩,洲閣篩等各類神奇的篩法。
(PS:後面這兩個篩法我是真的不會了QAQ)
- 要是不會,那就要少十分左右!
- 因此,專門用杜教篩來推式子的題目不多,通常都是用杜教篩優化線性篩,弄到最後的那些分。
- 不過,杜教篩的思想對於推式子是頗有幫助的。(它那種遞歸求解的形式,以及複雜度\(O(n^\frac{2}{3})\) )
- 所以,學會杜教篩也是一件挺好的事情!
前置技能
各類函數
概念
- 首先,咱們須要知道有一個東西叫作數論函數
- 數論函數有不少種,可是咱們身爲Oier,並不須要知道它的具體的定義,具體的分類。
- 咱們只須要知道,咱們在OI中的數論中所用到的各類函數\(\mu,\varphi\)等都是數論函數。(後面會將常見的都列舉出來,固然不僅這兩種)。
- 當你瞭解數論函數後,你就須要知道有一類函數叫作積性函數。
- 仍是一樣的話語,咱們日常所慣用的數論函數都是積性函數!
- 不過,對於積性函數的定義仍是有必要了解一下。(畢竟有些函數看上去不常見,其實可能就是積性函數!)
- 積性函數定義:若是已知一個函數爲數論函數,且\(f(1)=1\),而且知足如下條件,若對於任意的兩個互質的正整數\(p,q\)都知足\(f(p\cdot q)=f(p)\cdot f(q)\),那麼則稱這個函數爲積性函數。
- 特殊的,若是當對於任意的正整數\(p,q\)(即不必定互質),也知足以上這個式子,則稱這個函數爲徹底積性函數。
- 而咱們的杜教篩,則是用來篩積性函數前綴和的神奇篩法!!!
- 說了這麼多概念性的東西,不如來點實質性的!
常見積性函數
- \(\mu(n)\)——莫比烏斯函數。關於這個函數,我在莫比烏斯反演中說的挺清楚的了(233),(PS:不過我將會在下文中,從另外一種角度介紹它的性質。也算是把坑給填完吧)
- \(\varphi(n)\)——歐拉函數。表示不大於\(n\)且與\(n\)互質的正整數個數,十分常見的數論函數。用數學式子表示即:\(\varphi(n)=\sum_{i=1}^{n}[(n,i)=1]\) (PS:\((n,i)\)表示\(gcd(n,i)\))
- \(d(n)\)——約數個數。表示\(n\)的約數的個數。用式子表示爲:\(d(n)=\sum_{d|n}1\),也能夠寫做:\(d(n)=\sum_{d=1}^{n}[d|n]\) (其實沒什麼太大區別啦!)
- \(\sigma(n)\)——約數和函數。 即\(n\)的各個約數之和。表示爲:\(\sigma(n)=\sum_{d|n}d=\sum_{d=1}^{n}[d|n]\cdot d\)
(PS:接下來列舉的是徹底積性函數)
(PS:表明字母可能會與他人的略有不一樣,彷佛在數學中沒有統一的字母)c++
- \(\epsilon(n)\)——元函數。彷佛也有人把它叫做\(e(n)\)?其實無所謂啦~~咱們只須要知道\(\epsilon(n)=[n=1]\)。(看到這個是否是有種莫名的熟悉感呢?到了下文中,就會發現這種熟悉感是從哪來的啦!)
- \(I(n)\)——恆等函數。所謂恆等就是這個函數的值恆爲\(1\)。
- \(id(n)\)——單位函數。\(id(n)=n\)。
(當第一次看到這些徹底積性函數的時候,是否是有人感受這些徹底積性函數毫無用處,都是一些簡單的式子,只不過用符號表示了呢?在下一個前置技能——狄利克雷卷積中,你應該就會改變本身\(naive\)的想法啦~)git
狄利克雷卷積 (\(*\))
基本知識
- 聽名字,彷佛是一個很高深的東西。
- 其實,如果不理睬這個名字,只是把它看成一個新定義的符號,你應該就會發現,狄利克雷卷積也不是那樣的難理解。
- 定義:兩個數論函數\(f\)和\(g\)的卷積爲\((f*g)(n)=\sum_{d|n}f(d) \cdot g(\frac{n}{d})\)。前面的括號表明將\(f\)卷\(g\),後面的括號表明範圍。(PS:後面的括號通常能夠省略不寫,默認爲\(n\))
- 很顯然,狄利克雷卷積知足如下運算規律:
- 交換律(\(f*g=g*f\));
- 結合律(\((f*g)*h=f*(g*h)\));
- 分配律(\((f+g)*h=f*h+g*h\));
- 在記憶方面,能夠類比爲乘法的運算法則,其實上面這幾條運算規律是能夠證實的!
- 舉個例子,交換律。咱們看狄利克雷卷積的式子,實質上就是\(n\)的每個約數帶入\(f\)中的值,乘上與之對應的約數在\(g\)中的值。
- 顯然,當交換\(f\)和\(g\)時,僅僅時枚舉約數的順序發生了改變,而每個約數對答案的貢獻是不會有改變的。所以存在交換律!
- 在大體瞭解了狄利克雷卷積的運算法則後,咱們就須要提到上面所說的積性函數啦!
- 首先,元函數 \(\epsilon\)。所謂元函數,指的就是在狄利克雷卷積中充當單位元的做用,單位元即知足:\(f*\epsilon=f\)。不要小看這個元函數,當元函數配合上結合律時就能夠用來證實一些結論啦~
- 除了元函數以外,咱們最爲常見的則是\(\mu,\varphi\)之類的的函數,所以咱們須要十分熟練它們與一些常見的徹底積性函數的卷積,以及性質。
(PS:特別要記住一點:積性函數有一個特別重要的性質,那就是(積性函數\(*\)積性函數)仍然爲積性函數!!!這個性質能夠用來判斷可否被杜教篩!)算法
莫比烏斯函數\(\mu\)
- \(\mu\)。在莫比烏斯反演中,咱們曾瞭解過一個與\(\mu\)有關的性質:\(\sum_{d|n}\mu(d)=[n=1]\)
- 咱們將這個性質表示成狄利克雷卷積的形式即:\(\mu*I=\epsilon\)。這在狄利克雷卷積中是一個很經常使用的恆等式。固然,有了它,咱們也可以證實出莫比烏斯反演啦!
- 開始填坑,證實莫比烏斯反演:
已知:
\[F(n)=\sum_{d|n}f(d)\]
用狄利克雷卷積的形式表示這個式子即:\(F=f*I\)
利用狄利克雷卷積將\(F\)捲上\(\mu\),獲得:
\[F*\mu=f*I*\mu\]
因爲狄利克雷卷積具備結合律與交換律,所以原式可化爲:
\[\to f*(I*\mu)=f*\epsilon=f\]
即:\(f=F*\mu\)。代入後便可證實莫比烏斯反演:\(f(n)=\sum_{d|n}\mu(d)\cdot F(\frac{n}{d})\)
同理,天然也能夠獲得莫比烏斯反演的另外一種形式:\(f(n)=\sum_{n|d}\mu(\frac{d}{n})\cdot F(d)\)
(總算填完一個大坑......)
歐拉函數 \(\varphi\)
- \(\varphi\)。歐拉函數有一個很著名的性質:\(\sum_{d|n}\varphi(d)=n\)。
- 與以上方法相似,咱們將它表示成狄利克雷卷積的形式:\(\varphi*I=id\)。
- 這時候,看到這個式子咱們會有一個大膽的想法,既然在這個歐拉函數與莫比烏斯函數的式子中都有\(I\),那麼咱們不如將這個式子的兩邊同時捲上一個\(\mu\)。
- 因而,我就能夠開始填第二個坑了——歐拉函數與莫比烏斯函數的關係。
\[\varphi*I=id \\\to \varphi*I*\mu=id*\mu \\\to \varphi*\epsilon=id*\mu\]
即:\(\varphi=id*\mu \to \varphi(n)=\sum_{d|n}\mu(d)\cdot \frac{n}{d}\)
- 咱們把這個式子的兩邊同時除以\(n\),則能夠推出這個巧妙的式子:
\[\frac{\varphi(n)}{n}=\sum_{d|n}\frac{\mu(d)}{d}\]
(至此,我終於把莫比烏斯反演中的坑填完啦~~23333)
(有關杜教篩的前置技能也說的差很少啦,終於能夠步入正題啦!)
步入正題——杜教篩
- 說了這麼久,終於能夠開始講杜教篩啦!(是否是有一種莫名的興奮呢?)
- 首先,咱們應該弄清楚一個問題:杜教篩究竟是用來幹什麼的?
- 杜教篩是以低於線性的時間複雜度來計算積性函數的前綴和的神奇篩法!
- 即咱們須要計算的式子爲:\(\sum_{i=1}^{n}f(i)\) (\(f(i)\)爲積性函數)
- PS:接下來要講解的是杜教篩的套路式,若是不懂爲何要這樣作,也沒有關係。只須要明白它是怎麼推過來的就好了。實在看不懂就記個結論吧......
- 推式子時間到!
- 爲了解決這個問題,咱們構造兩個積性函數\(h\)和\(g\)。使得\(h=f*g\)
- 如今咱們開始求\(\sum_{i=1}^{n}h(i)\)。
- 記\(S(n)=\sum_{i=1}^{n}f(i)\)。
\[\sum_{i=1}^{n}h(i)=\sum_{i=1}^{n}\sum_{d|i}g(d)\cdot f(\frac{i}{d})\\\to =\sum_{d=1}^{n}g(d)\cdot\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f({i})\]
\[\to \sum_{i=1}^{n}h(i)=\sum_{d=1}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)\]
接着,咱們將右邊式子的第一項給提出來,能夠獲得:
\[ \sum_{i=1}^{n}h(i)=g(1)\cdot S(n)+\sum_{d=2}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)\]
\[\to g(1)S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)\]
其中的\(h(i)=(f*g)(i)\);
- 這就是杜教篩的慣用套路式。經各類分析,只要當你的\(h(i)\)的前綴和很好求,能在較短的時間內求出,那麼當咱們對後面的式子進行整除分塊時,求\(S(n)\)的複雜度爲\(O(n^{\frac{2}{3}})\)
- 當咱們知道了這個套路式後,可能會思考,咱們應該如何選擇這個\(g\)與\(h\)呢?
- 對於這個疑問,我沒有太好的回答。只能說,依靠平時對於狄利克雷卷積中的各類式子的熟悉,以及仔細觀察式子的能力啦!(固然我下面也會介紹一種小方法啦~~OVO)
- 知道了這個套路式總要練練手吧!
應用
(PS:如下例子中,假設線性篩均跑不過)jsp
一:求\(S(n)=\sum_{i=1}^{n}\mu(i)\);函數
- 根據那個套路式:\(g(1)S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{d=2}^{n}g(d)\cdot S(\lfloor\frac{n}{d}\rfloor)\),咱們只須要找一個積性函數\(g\)使得這個函數與\(\mu\)的卷積的前綴和容易求。若是你認真的看了上文,應該就能夠很輕鬆的想到一個積性函數\(I\)。
- 咱們知道\(\mu*I=\epsilon\),很顯然,單位元的前綴和很是好求,就是\(1\),而且\(I\)十分方便整除分塊。因此咱們把這個積性函數帶入上述式子中能夠獲得:
\[S(n)=1-\sum_{d=2}^{n}S(\lfloor\frac{n}{d}\rfloor)\]
所以,咱們就學會了杜教篩莫比烏斯函數的前綴和啦!
二:求\(S(n)=\sum_{i=1}^{n}\varphi(i)\)學習
- 與求莫比烏斯函數的思路相似。
- 咱們在腦海中找到一個與歐拉函數有關的卷積式子:\(\varphi*I=id\)
- 咱們能夠發現,在篩歐拉函數前綴和所選擇的積性函數\(g\)一樣也是\(I\)喲!代入得:
\[S(n)=\sum_{i=1}^{n}i-\sum_{d=2}^{n}S(\lfloor\frac{n}{d}\rfloor)\]
前面那個式子能夠利用等差數列求和公式\(O(1)\)的計算出結果,後面一樣利用整除分塊。
因此,咱們又學會了如何篩歐拉函數的前綴和啦!
三:求\(S(n)=\sum_{i=1}^{n}i\cdot \varphi(i)\)優化
- 這個式子是否是沒法一眼看出須要配什麼積性函數了呢?
- 咱們考慮狄利克雷卷積的形式:\(\sum_{d|n}(d\cdot\varphi(d))\cdot g(\frac{n}{d})\)
- 咱們看前面這個\(d\)不太爽,考慮後面配出一個積性函數使得這個\(d\)可以被約掉。所以,咱們嘗試將\(g\)配成\(id\)。這樣就能夠把\(d\)給弄沒!代入得:
\[\sum_{d|n}(d\cdot\varphi(d))\cdot \frac{n}{d}=\sum_{d|n}n\cdot\varphi(d)\\\to=n\sum_{d|n}\varphi(d)=n^2\]
咱們驚喜的發現,彷佛配對了!!!
得:
\[S(n)=\sum_{i=1}^{n}i^2-\sum_{d=2}^{n}d\cdot S(\lfloor\frac{n}{d}\rfloor)\]
對於這個式子,咱們前面能夠利用平方和的公式\(O(1)\)算出結果,後面的式子利用等差數列求和公式進行整除分塊。
所以,咱們能夠經過以上的思路求得這個看似沒法篩的積性函數的前綴和!
代碼實現
- 至於在信息學中的代碼實現,我給出一個大概的思路:咱們首先先線篩出數據範圍根號左右的積性函數的前綴和。再遞歸的實現杜教篩。
- 特別要注意的是,杜教篩篩出的前綴和必定要存下來!!!
- 若是你比較的勤勞,那就去手寫hash,若是你想偷懶,那就最好用stl中的unordered_map,最好不要用map,無緣無故多個log的複雜度,何須呢......
- 還有一點,必定要記得取模!!!以及,判斷要不要開long long,搞很差你TLE就是由於取模去多了,或者long long開多啦!
- 有評論區的大佬提醒我,說這份代碼被卡了,我調了一下前面線篩的範圍,有必定的加速,最後發現,果真是開long long的鍋,如今已經將代碼改正,是沒有問題的啦!
- 在這裏我就粘一下本身杜教篩\(\mu\)和\(\varphi\)的板子吧。這種東西最好本身手打一遍,否則你一沒注意,常數一大,就很麻煩啦!(反正我寫這個東西,常數巨大)
- 所以,代碼僅供參考!luoguP4213杜教篩模板
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define N 6000010
using namespace std;
template<typename T>inline void read(T &x)
{
x=0;
static int p;p=1;
static char c;c=getchar();
while(!isdigit(c)){if(c=='-')p=-1;c=getchar();}
while(isdigit(c)) {x=(x<<1)+(x<<3)+(c-48);c=getchar();}
x*=p;
}
bool vis[N];
int mu[N],sum1[N],phi[N];
long long sum2[N];
int cnt,prim[N];
tr1::unordered_map<long long,long long>w1;
tr1::unordered_map<int,int>w;
void get(int maxn)
{
phi[1]=mu[1]=1;
for(int i=2;i<=maxn;i++)
{
if(!vis[i])
{
prim[++cnt]=i;
mu[i]=-1;phi[i]=i-1;
}
for(int j=1;j<=cnt&&prim[j]*i<=maxn;j++)
{
vis[i*prim[j]]=1;
if(i%prim[j]==0)
{
phi[i*prim[j]]=phi[i]*prim[j];
break;
}
else mu[i*prim[j]]=-mu[i],phi[i*prim[j]]=phi[i]*(prim[j]-1);
}
}
for(int i=1;i<=maxn;i++)sum1[i]=sum1[i-1]+mu[i],sum2[i]=sum2[i-1]+phi[i];
}
int djsmu(int x)
{
if(x<=6000000)return sum1[x];
if(w[x])return w[x];
int ans=1;
for(int l=2,r;l>=0&&l<=x;l=r+1)
{
r=x/(x/l);
ans-=(r-l+1)*djsmu(x/l);
}
return w[x]=ans;
}
long long djsphi(long long x)
{
if(x<=6000000)return sum2[x];
if(w1[x])return w1[x];
long long ans=x*(x+1)/2;
for(long long l=2,r;l<=x;l=r+1)
{
r=x/(x/l);
ans-=(r-l+1)*djsphi(x/l);
}
return w1[x]=ans;
}
int main()
{
int t,n;
read(t);
get(6000000);
while(t--)
{
read(n);
printf("%lld %d\n",djsphi(n),djsmu(n));
}
return 0;
}
總結
- 固然,杜教篩還可以篩許多東西,如:\(\sum_{i=1}^{n}i^2\cdot\mu(i)\)之類的一系列積性函數,在這裏就不一一列舉啦。
- 其實,通常來講篩的就是那些經常使用的積性函數。
- 若是實在碰到相似與上面那個沒法一眼看出結果的式子,咱們就能夠採用剛剛例三的思路.
- 先考慮將那些特殊性質不明顯的數弄掉,再嘗試猜積性函數。固然不必定一試就中,可是隻要咱們有足夠的耐心與信念,相信這個題目所給的必定能篩,就必定能試出來233.
- 固然還有一種方法,從常見的徹底積性函數開始試,若是都不行,在嘗試一下高次的徹底積性函數,以後嘗試非徹底積性函數(雖然說通常都不是這個。。。),若是仍是不行,那就算了吧,(反正就那一點分麼。。。),試不出,技不如人,甘拜下風233.
題目
- 題目能夠去51nod上找,那上面杜教篩的題目挺多的,我就不粘地址啦!
- 洛谷上也有模板題!
- 固然,洛谷上也有須要推式子的題目,我之後有時間再加吧!