1. 程式人生 > >(轉載)O(N)的素數篩選法和歐拉函數

(轉載)O(N)的素數篩選法和歐拉函數

算法與數據結構 變形 技術 範圍 n) border {} 數據 eps

轉自:http://blog.csdn.net/dream_you_to_life/article/details/43883367 作者:Sky丶Memory

1.一個數是否為質數的判定.

質數,只有1和其本身才是其約數,所以我們判定一個數是否為質數,只需要判定2~(N - 1)中是否存在其約數即可,此種方法的時間復雜度為O(N),隨著N的增加,效率依然很慢。這裏有個O(技術分享)的方法:對於一個合數,其必用一個約數(除1外)小於等於其平方根(可用反證法證明),所以我們只需要判斷2~技術分享之間的數即可.

1 bool is_prime(int num)  
2 {  
3     const int
border = sqrt(num); 4 for (int i = 2; i <= border; ++i) 5 if (num % i == 0) 6 return false; 7 return 1 != num; 8 }


2.一個數的質因數分解

對於一個數N的質因數分解,簡單一點的方法通過枚舉2~N之間的每個數字,如果N值能整除當前枚舉的數,則將N值除盡,重復上面的步驟,直到結束.我們可以看出此種方法的時間復雜度為O(N),而我們通過上面介紹的方法,可以將時間復雜度降為O(技術分享),原理與判定一個數是否為質數是一樣的.

 1 map<int, int> factor(int num)  
 2 {  
 3     map<int, int> ans;  
 4     const int border = sqrt(num);  
 5     for (int i = 2; i <= border; ++i)  
 6         while (num % i == 0)  
 7             ++ans[i], num /= i;  
 8     if (num > 1)  
 9         ans[num] = 1;  
10     return
ans; 11 }   

3.歐拉函數

在數論中,對正整數n,歐拉函數是小於或者等於n的數中與n互質的數的個數.假設n的唯一分解式為技術分享,根據容斥原理可知

技術分享

對於{p1,p2,....,pk}的任意子集S,“不與其中任何一個互述素”的元素個數為技術分享。不過這一項的前面是加號還是減號呢?取決於S中元素的個數-———奇數個數就是"減號”,偶數個數就是“加號”,如果對這個地方有疑問的,可以參考下組合數學容斥原理的章節.

現在我們得到了計算歐拉函數的公式,不過這樣計算起來非常麻煩。如果根據公式直接計算,最壞情況下需要計算技術分享項的多項式。不過這點倒不用我們擔心,前人已經在此公式上面已經做了相應的研究,這裏直接給出公式的變形,上述公式可以變形成如下的公式:

技術分享

從而我們計算某個數的歐拉函數,只需要O(K)的計算時間,在剛才原始的基礎上大大提高了效率。如果題目中沒有給出唯一分解式,我們可以根據第二個小節的做法,在技術分享的時間復雜度解決這個問題.

 1 int euler(int n)  
 2 {  
 3     const int border = sqrt(n);  
 4     int cnt = n;  
 5     for (int i = 2; i <= border; ++i)  
 6     {  
 7         if (n % i == 0)  
 8         {  
 9             cnt = cnt / i * (i - 1);  
10             while (n % i == 0)  
11                 n /= i;  
12         }  
13     }  
14     if (n > 1)  
15         cnt = cnt / n * (n - 1);  
16     return cnt;  
17 }  

上面介紹了一些關於素數和歐拉函數的小知識點,那現在進入主題——如何在O(N)的時間復雜度內求出某段範圍的素數表.在ACM比賽中,有些題目往往需要求出某段範圍內素數,而此時如何高效的求出素數表就顯得尤為重要。關於素數表的求法,比較出名的是埃氏素數篩選法。其基本原理是每找到一個素數,將其倍數的數從素數表中刪除,不斷重復此過程,最終表中所剩數據全部為素數。下面的gif圖片展示了該方法的相應步驟:

技術分享

O(N)的素數篩選法和歐拉函數

埃氏素數篩選法的寫法有多種版本,其時間復雜度為技術分享,這裏給出一份實現代碼.

 1 const int N = 1e+6 + 7;  
 2 bool prime[N];  
 3 void init_prime_table(int n)  
 4 {  
 5     const int border = sqrt(n);  
 6     memset(prime, true, sizeof(prime));  
 7     prime[0] = prime[1] = false;  
 8     for (int i = 2; i <= border; ++i)  
 9     {  
10         if (!prime[i])  
11             continue;  
12         //此處j值需要註意溢出的bug  
13         for (long long j = i * i; j <= n; j += i)  
14             prime[j] = false;  
15     }  
16 }  

一般情況下,對於大部分的題目上面的寫法已經夠用了.然而,有人將上述的方法優化到了技術分享,效率雖然沒有很大數量級的提升,不過,思想還是值得學習的.學過數學知識的人大都知道,對於一個正整數,如果其為合數,那麽該數的質因數分解形式是唯一的。假設一個合數n的質因數分解形式為:

技術分享

現定義:對於某個範圍內的任意合數,只能由其最小的質因子將其從表中刪除。我們很容易得出該算法的時間復雜度為線性的,為什麽呢?因為一個合數的質因數分解式是唯一的,而且我們定義了合數只能由最小質因子將其從表中刪除,所以每個合數只進行了一次刪除操作(需要註意的是:埃氏素數篩選法中合數可能被多個質數刪除,比如12,18等合數).現在原始的問題轉換為怎麽將合數由其最小的質因子刪除?我們考查任何一個數n,假設其最小質因子為m,那麽小於等於m的質數與n相乘,會得到一個更大的合數,且其最小質因數為與n相乘的那個質數,而該合數可以直接從表中刪除,因為其剛好滿足之前的合數刪除的定義,所以我們需要維護一個表用來記錄已經找到了的質數,然後根據剛才敘述的步驟執行,就能將埃氏素數篩選法的時間復雜度降為技術分享.

 1 const int N = 1e+6 + 7;  
 2 bool prime[N];  
 3 int rec[N], cnt;  
 4 void init_prime_table(int n)  
 5 {  
 6     cnt = 0;  
 7     memset(prime, true, sizeof(prime));  
 8     prime[0] = prime[1] = false;  
 9     for (int i = 2; i <= n; ++i)  
10     {  
11         if (prime[i])  
12             rec[cnt++] = i;  
13         //此處邊界判斷為rec[j] <= n / i,如果寫成i * rec[j] <= n,需要確保i * rec[j]不會溢出int  
14         for (int j = 0; j < cnt && rec[j] <= n / i; ++j)  
15         {  
16             prime[i * rec[j]] = false;  
17             if (i % rec[j] == 0)  
18                 break;  
19         }  
20     }  
21 } 

同樣的,通過此種方法,我們可以在線性的時間生成某段範圍的歐拉函數表,原理與上述類似,這裏就不做過多的解釋了。

(轉載)O(N)的素數篩選法和歐拉函數