前言:好多學ACM的人都在問我數論的知識(其實我本人分不清數學和數論有什么區別,反正以后有關數學的知識我都扔進數論分類里面好了)
於是我就准備寫一個長篇集,把我知道的數論知識和ACM模板都發上來(而且一旦模板有更新,我就直接在博客上改了,所以記得常來看看(。・ω・))
廢話說完了,直接進入正題ヾ(=^▽^=)ノ
素數,又叫質數,定義是除了1和它本身以外不再有其他的因數
我們通過這個定義,可以寫如下程序判斷一個數是不是質數
1 bool prime(int x){//判斷x是不是質數,是返回true,不是返回false 2 if(x <= 1) return false; 3 for(int i = 2; i < x; i ++){ 4 if(x % i == 0) return false; 5 } 6 return true; 7 }
這個程序的時間復雜度是O(n),有沒有更快的方法,當然
看這個
1 bool prime(int x){//判斷x是不是質數,是返回true,不是返回false 2 if(x <= 1) return false; 3 for(int i = 2; i <= sqrt(x + 0.5); i ++){//0.5是防止根號的精度誤差 4 if(x % i == 0) return false; 5 } 6 return true; 7 } 8 //另一種方法,不需要根號 9 bool prime(int x){//判斷x是不是質數,是返回true,不是返回false 10 if(x <= 1) return false; 11 for(int i = 2; i * i <= x; i ++){//用乘法避免根號的精度誤差 12 if(x % i == 0) return false; 13 } 14 return true; 15 } 16 //根據題目不同,如果i*i會爆int,記得開longlong
這個復雜度是O(√n),速度快多了(#°Д°)
根據題目不同,有可能你需要先預處理出1~N這N個數是否是素數
如果用剛剛的方法,復雜度就是O(n√n)
1 #include<cstdio> 2 const int N = 100000 + 5; 3 bool prime[N]; 4 bool is_prime(int x){ 5 if(x <= 1) return false; 6 for(int i = 2; i * i <= x; i ++){ 7 if(x % i == 0) return false; 8 } 9 return true; 10 } 11 void init(){ 12 for(int i = 0; i < N; i ++){ 13 prime[i] = is_prime(i); 14 } 15 } 16 int main(){ 17 init(); 18 }
如果n大一點,就太慢了(。・ω・)ノ゙
介紹一種新方法,埃篩
埃篩--------------埃拉托斯特尼篩法,或者叫埃氏篩法
原理:如果找到一個質數,那么這個質數的倍數都不是質數
比如2是質數,那么4,6,8,10,12...都不是質數
然后看3是質數,那么6,9,12,15,18,21...都不是質數
然后看4,4已經被2標記為合數了,所以跳過
然后看5......這樣一直篩下去
1 #include<cstdio> 2 const int N = 100000 + 5; 3 bool prime[N]; 4 void init(){ 5 for(int i = 2; i < N; i ++) prime[i] = true;//先全部初始化為質數 6 for(int i = 2; i < N; i ++){ 7 if(prime[i]){//如果i是質數 8 for(int j = 2*i; j < N; j += i){//從i的兩倍開始的所有倍數 9 prime[j] = false; 10 } 11 } 12 } 13 } 14 int main(){ 15 init(); 16 }
因為一些數字,比如6既被2的for循環經過又被3的for循環經過,所以復雜度不是O(n)
這個復雜度經過專業人士檢驗,復雜度O(nloglogn)(學過高數的小朋友可以自己證明≖‿≖✧當然也可以去百度)
知道原理后,我們再稍微優化一下就更快了
1 #include<cstdio> 2 const int N = 100000 + 5; 3 bool prime[N]; 4 void init(){ 5 for(int i = 2; i < N; i ++) prime[i] = true; 6 for(int i = 2; i*i < N; i ++){//判斷改成i*i<N 7 if(prime[i]){ 8 for(int j = i*i; j < N; j += i){//從i*i開始就可以了 9 prime[j] = false; 10 } 11 } 12 } 13 } 14 int main(){ 15 init(); 16 }
好戲都是要留到最后的≖‿≖✧確實還有O(n)的做法
這個算法名字叫線篩
1 #include<cstdio> 2 const int N = 100000 + 5; 3 bool prime[N];//prime[i]表示i是不是質數 4 int p[N], tot;//p[N]用來存質數 5 void init(){ 6 for(int i = 2; i < N; i ++) prime[i] = true;//初始化為質數 7 for(int i = 2; i < N; i++){ 8 if(prime[i]) p[tot ++] = i;//把質數存起來 9 for(int j = 0; j < tot && i * p[j] < N; j++){ 10 prime[i * p[j]] = false; 11 if(i % p[j] == 0) break;//保證每個合數被它最小的質因數篩去 12 } 13 } 14 } 15 int main(){ 16 init(); 17 }
這個方法可以保證每個合數都被它最小的質因數篩去
所以一個數只會經過一次
時間復雜度為O(n)
其實loglogn非常小,把埃篩看成線性也無妨,畢竟它比線篩好寫
基於埃篩的原理,我們可以用它干很多事
比如預處理每個數的所有質因數

1 #include<cstdio> 2 #include<vector> 3 using namespace std; 4 const int N = 100000 + 5; 5 vector<int > prime_factor[N]; 6 void init(){ 7 for(int i = 2; i < N; i ++){ 8 if(prime_factor[i].size() == 0){//如果i是質數 9 for(int j = i; j < N; j += i){ 10 prime_factor[j].push_back(i); 11 } 12 } 13 } 14 } 15 int main(){ 16 init(); 17 }
比如預處理每個數的所有因數

1 #include<cstdio> 2 #include<vector> 3 using namespace std; 4 const int N = 100000 + 5; 5 vector<int > factor[N]; 6 void init(){ 7 for(int i = 2; i < N; i ++){ 8 for(int j = i; j < N; j += i){ 9 factor[j].push_back(i); 10 } 11 } 12 } 13 int main(){ 14 init(); 15 }
比如預處理每個數的質因數分解

1 #include<cstdio> 2 #include<vector> 3 using namespace std; 4 const int N = 100000 + 5; 5 vector<int > prime_factor[N]; 6 void init(){ 7 int temp; 8 for(int i = 2; i < N; i ++){ 9 if(prime_factor[i].size() == 0){ 10 for(int j = i; j < N; j += i){ 11 temp = j; 12 while(temp % i == 0){ 13 prime_factor[j].push_back(i); 14 temp /= i; 15 } 16 } 17 } 18 } 19 } 20 int main(){ 21 init(); 22 }
世界之大無奇不有(。-`ω´-)數論是個可怕的東西