尤拉函式
定義
對於任意的正整數 \(n\),尤拉函式 \(\phi(n)\) 表示小於等於 \(n\) 的所有數中與 \(n\) 互質的數的個數。
暴力實現
那麼根據定義,不難直接打出一個時間複雜度 \(O(n)\) 的程式碼,列舉所有小等於 \(n\) 的數字 \(i\),若 \(\gcd(n,i)=1\) 則答案 \(+1\)。
程式碼:
#include<bits/stdc++.h>
using namespace std;
int n,ans;
int gcd(int x,int y)
{
return !y?x:gcd(y,x%y);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
if(gcd(i,n)==1)
ans++;
printf("%d\n",ans);
return 0;
}
很顯然,這段程式碼效率不夠高。有沒有什麼可以最佳化的地方?
尤拉函式推導
先根據定義可知以下幾點:
- \(\phi(1)=1\)
- 若 \(n\) 為質數,則 \(\phi(n)=n-1\),即除了 \(n\) 自己外全都算。
- 若 \(n=p^k\),則 \(\phi(n)=p^k-p^{k-1}\)。因為只有一個數不含質數 \(p\),才可以與 \(n\) 互質。所以就要剪掉包含質數 \(p\) 的那 \(p^{k-1}\) 個數字。而 \(p^k-p^{k-1}=p^k(1-\frac{1}{p})=p^k\frac{p-1}{p}\)。
- 若 \(n=ab\),其中 \(a,b\) 互質,則 \(\phi(n)=\phi(a)\times\phi(b)=(a-1)\times(b-1)\)。反過來也一樣,\(\phi(ab)=\phi(a)\times\phi(b)\)。
- 若 \(n=p_1^{k_1}p_2^{k_2}\cdots p_i^{k_i}\cdots p_m^{k_m}\),則根據第三點,可表示為 \(p_1^{k_1}\frac{p_1-1}{p_1}p_2^{k_2}\frac{p_2-1}{p_2}\cdots p_m^{k_m}\frac{p_m-1}{p_m}\)。
再把 \(p_1^{k_1}p_2^{k_2}\cdots p_m^{k_m}\) 拎出來,發現正好等於 \(n\),所以結果就變成了:
\[\phi(n)=n(\frac{p_1-1}{p_1})(\frac{p_2-1}{p_2})\cdots (\frac{p_m-1}{p_m})
\]
根據這個公式,我們就可以列舉 \(p_i\) 去求解 \(\phi(n)\)。程式碼:
#include<bits/stdc++.h>
using namespace std;
int n,ans;
int main(){
scanf("%d",&n);
ans=n;
if(n==1)//特判
{
printf("1\n");
return 0;
}
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
{
ans=ans*(i-1)/i;//公式
while(!(n%i))//能整除就一直除下去
n/=i;
}
}
if(n>1)//如果n還沒有除盡
ans=ans*(n-1)/n;
printf("%d\n",ans);
return 0;
}