淺談演算法——杜教篩
首先我們給道題目:求\(\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}})\)