前言
蒟蒻最近准備狂補數學啦TAT
基於篩素數,可以同時快速求出歐拉函數。於是蒟蒻准備從這里入手,整理一下實現的思路。
篩素數及其一種改進寫法
傳統篩素數的做法(埃式篩)是,利用已知的素數,去篩掉含有此質因子的合數,十分巧妙。由於不是本文的重點,就只貼一下代碼吧
#include<cstdio>
#include<cmath>
#define R register int
const int N=100000000,SQ=sqrt(N);
bool f[N];
int main(){
R i,j;
for(i=2;i<=SQ;++i){
if(f[i])continue;
for(j=i<<1;j<N;j+=i)f[j]=1;
}
/*for(i=2;i<N;++i)
if(!f[i])printf("%d\n",i);*/
return 0;
}
復雜度不會證,不過較近似於線性(大概是\(O(n\log\log n)\)的樣子)。
實際上蒟蒻打了個表,N與篩的次數大概有這樣的關系
為什么是近似的呢?因為每個合數會被其多個質因子都篩一遍,所以並不是嚴格的。
於是我們要想辦法讓每個合數只被篩掉一次。如何實現呢?我們可以讓每個合數都只被其最小質因子篩掉(歐拉篩)。
與上面相比,這種新的更加優秀的寫法有了較大的變化。代碼如下,可結合注釋理解,也不多討論。
#include<cstdio>
#include<cmath>
#define R register int
const int N=100000000,B=N>>1;
bool f[N];
int pr[B];
int main(){
R i,j,p=0;
for(i=2;i<=B;++i){
if(f[i])
for(j=1;j<=p&&i*pr[j]<N;++j){
f[i*pr[j]]=1;
if(!(i%pr[j]))break;//這一句話就是使得每個合數只被最小質因子篩掉的關鍵
//簡要解釋一下,如果pr[j]|i,那么i就有一個質因子pr[j]
//那么{i*pr[j+k],k∈N*}的最小質因子就是pr[j]而不是pr[j+k]了
}
else{
pr[++p]=i;
for(j=1;j<=p&&i*pr[j]<N;++j)
f[i*pr[j]]=1;//i是質數,所以可以省掉上面那個判斷,減小常數
}
}
/*for(i=2;i<N;++i)
if(!f[i])printf("%d\n",i);*/
return 0;
}
實際運行\(N=10^8\)比上面那種寫法快一半。
歐拉函數
定義及性質
對一個正整數\(x\)定義歐拉函數\(\phi(x)\),為\([1,x]\)中與\(x\)互質的整數個數。
幾個基本內容(下面定義\(p\)為質數):
- \(\phi(1)=1\),這個沒啥好說的,因為本來就定義\(1\)與\(1\)互質
- \(\phi(p)=p-1\),也是很顯然的,由質數定義得出
- 如果\(p|x\),那么\(\phi(x*p)=\phi(x)*p\),否則\(\phi(x*p)=\phi(x)*(p-1)\)。證明的話百度一下吧,蒟蒻不會qwq
這樣的話,是不是可以像埃式篩一樣,通過某個質數篩去質因子含這個數的合數的同時,計算出這個合數的歐拉函數值呢?好像是不行的,因為可能兩個數的商的歐拉函數值還沒求出來。
這時候,更好的歐拉篩又派上了用場。枚舉\(x\)再枚舉質數\(p\),這時候\(\phi(x)\)和\(\phi(p)\)肯定都求出來啦,那么\(\phi(x*p)\)當然也就求出來啦。
#include<cstdio>
#define R register
const int N=1000001;
int pr[N],phi[N];
bool f[N];
int main(){
R int n,i,j,k,p=0;
phi[1]=1;//內容1
for(i=2;i<N;++i){
if(!f[i])phi[pr[++p]=i]=i-1;//內容2
for(j=1;j<=p&&(k=i*pr[j])<N;++j){
f[k]=1;
if(i%pr[j])//內容3
phi[k]=phi[i]*(pr[j]-1);
else{
phi[k]=phi[i]*pr[j];
break;
}
}
}
return 0;
}