杜教篩


杜教篩

(似乎有很多人在催我的杜教篩呢......)


前言

  • 話說,我是不是在自己的莫比烏斯反演中挖了許多杜教篩的坑啊......
  • 本文完整的總結介紹杜教篩,也算是將莫比烏斯反演中的坑全部填滿吧!
  • 真誠地希望來閱讀這篇學習筆記的每一個人,仔仔細細的看完每一段。
  • 我相信,只要認真的看完整篇文章並跟着一起思考的讀者,一定能夠有所收獲!
  • 如果您之前不會杜教篩,那么我希望這篇文章能夠作為您學習杜教篩路上的有力援助,幫助您真正的了解與掌握杜教篩!
  • 如果您之前早已熟知杜教篩或只有些模糊的印象,相信您一定也能有所收獲!
  • (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\)(即不一定互質),也滿足以上這個式子,則稱這個函數為完全積性函數
  • 而我們的杜教篩,則是用來篩積性函數前綴和的神奇篩法!!!
  • 說了這么多概念性的東西,不如來點實質性的!

常見積性函數

  1. \(\mu(n)\)——莫比烏斯函數。關於這個函數,我在莫比烏斯反演中說的挺清楚的了(233),(PS:不過我將會在下文中,從另一種角度介紹它的性質。也算是把坑給填完吧)
  2. \(\varphi(n)\)——歐拉函數。表示不大於\(n\)且與\(n\)互質的正整數個數,十分常見的數論函數。用數學式子表示即:\(\varphi(n)=\sum_{i=1}^{n}[(n,i)=1]\) (PS:\((n,i)\)表示\(gcd(n,i)\))
  3. \(d(n)\)——約數個數。表示\(n\)的約數的個數。用式子表示為:\(d(n)=\sum_{d|n}1\),也可以寫作:\(d(n)=\sum_{d=1}^{n}[d|n]\) (其實沒什么太大區別啦!)
  4. \(\sigma(n)\)——約數和函數。 即\(n\)的各個約數之和。表示為:\(\sigma(n)=\sum_{d|n}d=\sum_{d=1}^{n}[d|n]\cdot d\)

(PS:接下來列舉的是完全積性函數)
(PS:代表字母可能會與他人的略有不同,似乎在數學中沒有統一的字母)

  1. \(\epsilon(n)\)——元函數。似乎也有人把它叫作\(e(n)\)?其實無所謂啦~~我們只需要知道\(\epsilon(n)=[n=1]\)。(看到這個是不是有種莫名的熟悉感呢?到了下文中,就會發現這種熟悉感是從哪來的啦!)
  2. \(I(n)\)——恆等函數。所謂恆等就是這個函數的值恆為\(1\)
  3. \(id(n)\)——單位函數。\(id(n)=n\)

(當第一次看到這些完全積性函數的時候,是不是有人感覺這些完全積性函數毫無用處,都是一些簡單的式子,只不過用符號表示了呢?在下一個前置技能——狄利克雷卷積中,你應該就會改變自己\(naive\)的想法啦~)

狄利克雷卷積 (\(*\))

基本知識

  • 聽名字,似乎是一個很高深的東西。
  • 其實,若是不理睬這個名字,只是把它當作一個新定義的符號,你應該就會發現,狄利克雷卷積也不是那樣的難理解。
  • 定義:兩個數論函數\(f\)\(g\)的卷積為\((f*g)(n)=\sum_{d|n}f(d) \cdot g(\frac{n}{d})\)。前面的括號代表將\(f\)\(g\),后面的括號代表范圍。(PS:后面的括號一般可以省略不寫,默認為\(n\))
  • 很顯然,狄利克雷卷積滿足以下運算規律:
  1. 交換律(\(f*g=g*f\));
  2. 結合律(\((f*g)*h=f*(g*h)\));
  3. 分配律(\((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:以下例子中,假設線性篩均跑不過)

一:求\(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上找,那上面杜教篩的題目挺多的,我就不粘地址啦!
  • 洛谷上也有模板題!
  • 當然,洛谷上也有需要推式子的題目,我以后有時間再加吧!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM