1. 程式人生 > >【複習】尤拉函式

【複習】尤拉函式

首先讓我們來複習以下尤拉函式的概念。

  • 寫作\(phi(i)\),表示小於\(i\)的與\(i\)互質的數的個數
  • 特殊的,\(phi(1)=1\);

根據定義我們可以得到其推導方法。

  • 對於任意的\(i∈[2,INF]\),\(i\)都可以被拆分為\(p1^{c1}*p2^{c2}*...pn^{cn}\)的形式,其中\(pi\)表示素數,而\(ci\)表示素數的次數,即把一個數拆成素數乘積的形式。
  • 所以利用容斥原理,我們可得\(phi(i)\)的推導:\(phi(i)=N(1-1/p1)...(1-1/pn)\);

它有一些很優秀的性質:

  • \(phi\)是積性函式。對於任意滿足\(gcd(a,b)=1\)
    \(a,b\),都滿足下面這個式子:\(phi(ab)=phi(a)phi(b)\);
  • \(p|n\)\(p^2|n\),則有\(phi(n)=phi(n/p)*p\)
    • 證明:若滿足以上條件,則n和n/p的素陣列成相同,不同的只有次數。根據定義式,可以得到\(phi(n)=phi(n/p)*p\).
  • \(p|n\)\(p^2\)不被\(n\)整除,則有\(phi(n)=phi(n/p)*(p-1)\)
    • 證明:若滿足\(p|n\)\(p^2\)不被\(n\)整除,則\(n\)\(n/p\)互質。滿足\(phi(n)=phi(n/p)*phi(p)=phi(n)=phi(n/p)*(p-1)\)

那麼關鍵來了:我們要利用這些性質求解尤拉函式。

利用定義式,我們可以很容易想到常規推導:

    for(register int i=1;i<=n;++i)phi[i]=i;//先記為其本身
    for(register int i=2;i<=n;++i){
        if(phi[i]==i){//質數
            for(register int j=i;j<=n;j+=i){//處理後面的每一個i的倍數 
                phi[j]=phi[j]/i*(i-1);//利用定義式計算非積性求解情況 
            }
        }
    }
}

這個是建立在埃氏篩基礎上的尤拉函式求法,複雜度是O(nlogn),足以水過P2158 40000 的資料範圍。但是如果資料更大的話,這種演算法很顯然是不優秀的,我們就要考慮更快的演算法,於是便想到了同一個人名字命名的尤拉篩。(尤拉全家桶.jpg)

首先先考慮簡單的尤拉篩求素數集。

    vis[1]=1;//vis記錄素數情況,0為素數
    for(register int i=2;i<=n;++i){
         if(!vis[i])prime[++tot]=i;//素數
         for(register int j=1;j<=tot&&i*prime[j]<=n;++j){
               vis[i*prime[j]]=1;//合數標為1
               if(i%prime[j]==0)break;//j以後的都可以由更小的素數篩得
               //如i*prime[j+1]中i本身可以被分解為比prime[j+1]更小的質數。
         }
    }

同理,很容易就可以想到怎麼對尤拉函式求解了。

    for(register int i=1;i<=n;++i)phi[i]=i;
    for(register int i=2;i<=n;++i){
        if(phi[i]==i){//i為質數 
            prime[++cnt]=i;//記錄這個數位質數,最小質因子是它自己 
            phi[i]=i-1;
        }
        for(register int j=1;j<=cnt&&i*prime[j]<=n;++j){//不超出n的範圍
            if(i%prime[j]!=0){
                phi[i*prime[j]]=phi[i]*(prime[j]-1);
                //如果i%prime[j]!=0,則i*prime[j]和prime[j]互質 
            }else{
                phi[i*prime[j]]=phi[i]*(prime[j]);
                break;
            }
        } 
    }

就是這樣~

什麼時候會用到尤拉函式呢?通常我們要把它從複雜的模型裡提取出來。例如[P2158 SDOI2008]儀仗隊這個題目,就需要我們想到,對於任意一個首次出現的斜率,其gcd(x,y)一定為1即滿足互質。認真思考後就會發現完全就是一個尤拉函式求和啦~


\(UPD\):關於尤拉篩的那個\(break\)作用的考慮。

毫不誇張的說,尤拉篩中的那個\(break\)是整個演算法中最精華最讓人讚歎的地方。下面我們來考慮一下這種情況:

  • \(prime[ ]\)中儲存了所有的素數
  • 在尤拉篩中,一旦出現\(i\%prime[j]==0\),就在進行完本次運算後停止。
  • 原因:既然已經有\(i\%prime[j]=0\),那麼\(prime[j]\)就是\(i\)的本身組成。在繼續往後找的過程中,\(prime[j]\)只會越來越大。為了符合只讓\(i\)被其最小質因子篩掉一次的條件,我們在做完這次迴圈後就\(break\).