1. 程式人生 > 其它 >【數論】質數

【數論】質數

質數:①大於1;②不包含除了1和自身的其他約數。

試除法求解一個數是否為質數:

 1 bool isPrime(int n)
 2 {
 3     if(n == 1)
 4         return true;
 5     for(int i = 2;i < n;++i)
 6     {
 7         if(n % i == 0)
 8             return false;
 9     }
10     return true;
11 }

時間複雜度為O(N);

優化:考慮d|n,則(n/d)|n,即約數都是成對出現的,因此只需要列舉較小的一部分即可,設d≤(n/d),可得d≤sqrt(n),因此時間複雜度可以優化至O(n1/2

)。

 1 bool isPrime(int n)
 2 {
 3     if(n == 1)
 4         return true;
 5     for(int i = 2;i <= n/i;++i)
 6     {
 7         if(n % i == 0)
 8             return false;
 9     }
10     return true;
11 }

當然注意判斷條件,要麼寫成i≤n/i,要麼直接記錄下sqrt(n)的值然後放入判斷條件,不要寫成i*i≤n,兩個i相乘容易溢位,也不要寫成i≤sqrt(n),每一次都重算一次sqrt反而導致優化本末倒置了。

分解質因數

每一個合數都可以由若干個質數相乘得到,因此可以把一個合數分解成若干個質因數相乘的形式。

試除法

思想是這樣的,嘗試列舉所有的質因數,然後每次列舉到就將n除以這個質因數即可,只要能夠整除就繼續整除,那麼怎麼列舉所有的質因數呢?其實只要從2開始到n-1,列舉所有的數就可以了。

問題是這樣不會導致我們列舉到其他合數嗎?我們重新回到最初時的定義,每個合數都可以由質數相乘得到,當我們列舉完前面的質數時,其實意味著後面的合數其實已經被篩掉了。

舉個例子:

n = 60;

(i = 2) 60 % 2 == 0;60 / 2 = 30;

(i = 2) 30 % 2 == 0;30 / 2 = 15;

(i = 3) 15 % 3 == 0;15 / 3 = 5;

(i = 4) 5 % 4 != 0;

(i = 5) 5 % 5 == 0;5 / 5 = 1。

可以看到合數4在被列舉前,其實已經被它自己的質因數2給消去了,因此後續的合數是不會被列舉到的。

優化

考慮到n中其實最多隻包含一個大於n1/2的質因子,反證法即可證得,如果n中包含兩個或以上大於n1/2的質因子,那麼這兩個質因子一相乘得到的結果就大於n了,就矛盾了。

因此可以先列舉到n1/2,然後再看看最後的值是否大於1,如果大於1,則說明該值就是那個大於n1/2的質因子,再統計一次就可以了。

 1 void division(int x)
 2 {
 3     for(int i = 2;i <= x / i;++i)
 4     {
 5         int cnt = 0;
 6         while(x % i == 0)
 7         {
 8             x /= i;
 9             cnt++;
10         }
11         if(cnt)
12             cout << i << " " << cnt << endl;
13     }
14     if(x > 1)
15         cout << x << " " << 1 << endl;
16     cout << endl;
17 }

質數篩

篩出n以內的質數的方法有兩種,一種是埃拉託斯特尼篩法,簡稱埃氏篩,操作很簡單,從2開始到n,逐步篩去所有未被篩過的數的倍數,不好表述,直接看程式碼。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 const int N = 1000009;
 5 int prime[N],st[N];
 6 
 7 int main()
 8 {
 9     int n,cnt = 0;;
10     cin >> n;
11     for(int i = 2;i <= n;++i)
12     {
13         if(!st[i])  //如果當前數沒有被篩過,那麼它就是質數
14             prime[cnt++] = i;
15         for(int j = 2;j <= n/i;++j)//將當前質數的所有倍數都篩掉,注意篩掉的數不要超過n即可
16             st[i * j] = true;
17     }
18     cout << cnt << endl;
19     return 0;
20 }

另一種是線性篩,埃氏篩中其實存在多次重複篩選的過程,浪費時間,比如2能把6篩一次,3也會把6篩一次,因此對於一個合數,我們就是用它的最小質因數篩一次就可以了,因此線性篩的時間複雜度是線性的,即O(n)。

 1 #include <iostream>
 2 using namespace std;
 3 
 4 const int N = 1000009;
 5 int prime[N],st[N];
 6 
 7 int main()
 8 {
 9     int n,cnt = 0;;
10     cin >> n;
11     for(int i = 2;i <= n;++i)
12     {
13         if(!st[i])
14             prime[cnt++] = i;
15         for(int j = 0;prime[j] <= n/i;++j)
16         {
17             st[i * prime[j]] = true; //篩去prime[j]乘上i形成的合數
18             if(i % prime[j] == 0)  //當prime[j]是i的最小質因數,就可以break了,避免重複篩
19                 break;
20         }
21     }
22     cout << cnt << endl;
23     return 0;
24 }