1. 程式人生 > 實用技巧 >尤拉線性篩

尤拉線性篩

篩質數

關於尤拉篩篩質數,其總體思想:
· 首先,假設所有的數都是質數,然後通過篩選將合數一一篩去
· 為了確保可以線上性時間內篩去所有的合數(即對於每一個數只處理一次),每一個合數只由其最小的質因數篩去一次,從而避免一個合數被多次篩去而造成浪費時間。

那麼,具體的實現思路如下:

  1. 標記所有的數字為質數不用多說,開一個數組,所有數字記錄為true即可;
  2. 用一個for迴圈遍歷每一個數字;(注意一下,遍歷從“2”開始,因此“1”應當初始化為false);
  3. 假設當前這個數字是質數,即遍歷到這個數字的時刻,這個數字仍然被標記為true,則記錄這個質數,存入陣列中;
  4. 不論當前數字是質數還是合數,都要進行如下操作。易知,假設一個數字可以表示為兩個大於“1”的數字的乘積的形式,那麼這個數字一定是合數。因此,接下來就要篩掉所有因數中含該數字的合數。

這是核心部分,步驟4的具體程式碼:

for(int j=1;j<=prime[0]&&prime[j]*i<=size;++j){
	p[prime[j]*i]=false;
	if(i%prime[j]==0) break; //核心中較難理解的部分
}

挨行解釋:
第一行:關於for迴圈的條件,一是要如果條件允許就要遍歷每一個已知的質數;二是假如兩者乘積不能大於約定的範圍,畢竟超範圍了,“範圍”這個東西就沒意義了嗎。
第二行:這個就是篩掉這兩個數字的積的合數。
第三行:這個就是一個關鍵,有了它才能保證線性時間。為什麼當i是prime[j]就要break掉呢?尤拉篩的一個重要思想就是要求每一個合數都要由它的最小質因數篩去。假設當前的I是prime[j]的倍數,那麼i=n×prime[j] (n∈N*)一定成立。假如此刻不break掉,那麼下一個篩到下一個質數,prime[j+1]時,用同樣的i,需要篩去I×prime[j+1],而i=n×prime[j],因此I×prime[j+1]=n×prime[j]×prime[j+1],很明顯,這個合數的最小質因數是prime[j],而非prime[j+1],因此也就應該由prime[j]×某一個數字篩去,而不應該由prime[j+1]×i篩去,這樣就會與尤拉篩的思想不符合,導致時間複雜度增加。

至此,尤拉篩的篩質數部分就完成了,下面是完整程式碼(以篩100以內的質數為例):

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int phi[101],prime[101];
bool p[101];

int main(){
	for(int i=2;i<=100;++i) p[i]=true;
	for(int i=2;i<=100;++i){
		if(p[i]){
			prime[++prime[0]]=i;
		}
		for(int j=1;j<=prime[0]&&prime[j]*i<=100;++j){
			p[prime[j]*i]=false;
			if(i%prime[j]==0) break;
		}
	}
	return 0;
}

尤拉函式

在篩出質數的同時,我們也可以同時求出來每一個數字的尤拉函式。

科普一下:
尤拉函式指對於一個數字,對於小於等於該數字的所有正整數,與該數字互質的數字的個數。符號φ,中文讀音“斐",英文寫法:phi
特別地規定:φ(1)=1.

那麼,為了求可以在可以線上性時間內求出來所有的尤拉函式,需要事先知道三個性質:
對於一個質數p:
性質1:φ(p)=p-1;
因為p為質數,而從1到p,只有p與p自本身不互質,其餘的(p-1)個數字都與p互質,因此得出性質1;
性質2:對於i%p==0 , φ(ip)=pφ(i);
性質3:對於i%p!=0,φ(ip)=φ(i)(p-1);
利用了尤拉函式的積性。而又通過性質1可知,p-1=φ(p),因此φ(ip)=φ(i)(p-1)=φ(i)φ(p);
這個,本人才疏學淺,看了各種證明,但還是不明白性質2,性質3是怎麼得出來的,所以先當作結論記住吧。
知道了這三個結論之後,在配合尤拉篩篩質數,就可以得出範圍內所有的數字的尤拉函式。
程式碼:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

int phi[101],prime[101];
bool p[101];

int main(){
	phi[1]=1;
	for(int i=2;i<=100;++i) p[i]=true;
	for(int i=2;i<=100;++i){
		if(p[i]){
			prime[++prime[0]]=i;
			phi[i]=i-1;//改動部分1(對應性質1)
		}
		for(int j=1;j<=prime[0]&&prime[j]*i<=100;++j){
			p[prime[j]*i]=false;
			if(i%prime[j]==0){
				phi[i*prime[j]]=prime[j]*phi[i];//改動部分2(對應性質2)
				break;
			}
			else{
				phi[i*prime[j]]=phi[i]*phi[prime[j]];//改動部分3(對應性質3)
			}
		}
	}
	return 0;
}

這個應該挺淺顯易懂的,不過多解釋了
一個小補充:該演算法怎麼保證篩完之後,所有數字的phi值都被更新?
因為每一個數字要麼是質數,要麼是合數。
假如篩去了一個數字,即判斷該數字是合數,緊接著在接下來的程式碼中,就將該數字分類(性質1和性質2),並更新其phi值。
但是,假如一個數字被判斷為質數,那麼該數字phi值就會直接被更新為該數字-1,根據性質1.