【數論】質數
質數:①大於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 }