1. 程式人生 > >淺談演算法——杜教篩

淺談演算法——杜教篩

首先我們給道題目:求\(\sum\limits_{i=1}^n\mu(i)\)

\(n\leqslant 10^5\),我會\(O(n\sqrt{n})\)

\(n\leqslant 10^7\),我會\(O(n)\)線篩!

\(n\leqslant 10^9\),我……

於是杜教篩就此被髮明,它可以在\(O(n^{\frac{2}{3}})\)的時間內求出一些積性函式函式的字首和,如何做?

假定我們現在要求\(S(n)=\sum\limits_{i=1}^nf(i)\),於是我們找來一個積性函式\(g(i)\)(不知道是什麼東西),和\(f(i)\)卷積一下,有

\[(f*g)(n)=\sum\limits_{d|n}g(d)f(\dfrac{n}{d})\]

然後我們求一下卷積形式的字首和

\[\sum\limits_{i=1}^n(f*g)(i)=\sum\limits_{i=1}^n\sum\limits_{d|i}g(d)f(\dfrac{i}{d})\]

然後我們調整一下列舉順序,得到

\[\sum\limits_{d=1}^ng(d)\sum\limits_{d|i}f(\dfrac{i}{d})\]

\[\sum\limits_{d=1}^ng(d)\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)\]

\[\sum\limits_{d=1}^ng(d)S(\lfloor\dfrac{n}{d}\rfloor)\]

好,我們現在得到\(\sum\limits_{i=1}^n(f*g)(i)=\sum\limits_{d=1}^ng(d)S(\lfloor\dfrac{n}{d}\rfloor)\),然後我們把這個先放著,稍微思考一下,顯然有如下式子

\[g(1)S(n)=\sum\limits_{i=1}^ng(i)S(\lfloor\dfrac{n}{i}\rfloor)-\sum\limits_{i=2}^ng(i)S(\lfloor\dfrac{n}{i}\rfloor)\]

然後你發現中間一個和我們之前推出來的形式是一樣的,於是有

\[g(1)S(n)=\sum\limits_{i=1}^n(f*g)(i)-\sum\limits_{i=2}^ng(i)S(\lfloor\dfrac{n}{i}\rfloor)\]

如果中間卷積部分的字首和非常好算,那麼我們就可以對後面那部分進行數論分塊,然後遞迴處理,記得記憶化!

我們回到栗子,題目要求的\(f\)就是\(\mu\)對吧,代回去有

\[g(1)S(n)=\sum\limits_{i=1}^n(\mu*g)(i)-\sum\limits_{i=2}^ng(i)S(\lfloor\dfrac{n}{i}\rfloor)\]

現在我們需要找一個優秀的\(g\),使得他們狄利克雷卷積的字首和非常好算

我們知道

\[\sum\limits_{d|n}\mu(d)=[n=1]=e\]

所以\((1*\mu)=e\),你說\(e\)的字首和是啥,當然是1啦,於是我們取\(g(x)=1\),有

\[S(n)=1-\sum\limits_{i=2}^nS(\lfloor\dfrac{n}{i}\rfloor)\]

然後我們線篩出一部分\(\mu\)的字首和,再來一波記憶化搜尋,做完了


再舉個栗子,把\(\mu\)換成\(\varphi\)該怎麼做?

因為有

\[\sum\limits_{d|n}\varphi(d)=i=id(i)\]

所以還是取\(g(x)=1\),那麼得到

\(S(n)=\sum\limits_{i=1}^ni-\sum\limits_{i=2}^nS(\lfloor\dfrac{n}{i}\rfloor)\)

等差數列字首和\(O(1)\)求出就好了

對於不同的\(f\),只要找到合適的\(g\),就就可以讓你的程式變得非常好寫

不會找沒關係,打個表解決一切

不過記得不要有事沒事想著杜教篩,線篩啥的,埃氏篩法也是很有用的,\(O(n\ln n)\)列舉倍數也挺好的

騙分過樣例,暴力出奇跡


扯了這麼多,講下時間複雜度證明吧

其實可以發現除了\(S(n)=\sum\limits_{i=2}^nS(\lfloor\dfrac{n}{i}\rfloor)\)貢獻了複雜度外,其他基本上可以\(O(1)\)算出答案(噁心的\(g(n)\)不考慮)

那麼我們只要算出\(\sqrt n\)\(S(\lfloor\dfrac{n}{i}\rfloor)\)的值即可算出\(S(n)\),於是我們設\(T(n)\)為計算出\(S(n)\)的複雜度,那麼有

\[T(n)=O(\sqrt n)+\sum\limits_{i=1}^{\sqrt n}(T(i)+T(\dfrac{n}{i}))\]

其中\(O(\sqrt n)\)是累加合併的時間

然後我們展開一層,因為更深層的複雜度是高階小量,所以有

\[T(n)=\sum\limits_{i=1}^{\sqrt n}O(\sqrt i)+O(\sqrt{\dfrac{n}{i}})=O(n^{\frac{3}{4}})\]

但由於\(S(n)\)本身是可以通過線篩求出一部分的,假定我們預處理了前\(k\)個的值,且\(k\geqslant \sqrt n\),則複雜度變為

\[T(n)=\sum\limits_{i=1}^{\frac{n}{k}}\sqrt{\dfrac{n}{i}}=O(\dfrac{n}{\sqrt k})\]

則當\(k\)\(n^{\frac{2}{3}}\)時可以取到較好的複雜度\(T(n)=O(n^{\frac{2}{3}})\)