前言:好多學ACM的人都在問我數論的知識(其實我本人分不清數學和數論有什麼區別,反正之後有關數學的知識我都扔進數論分類裏面好了)算法
因而我就準備寫一個長篇集,把我知道的數論知識和ACM模板都發上來(並且一旦模板有更新,我就直接在博客上改了,因此記得常來看看(。・ω・))ide
廢話說完了,直接進入正題ヾ(=^▽^=)ノ優化
素數,又叫質數,定義是除了1和它自己之外再也不有其餘的因數spa
咱們經過這個定義,能夠寫以下程序判斷一個數是否是質數3d
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),有沒有更快的方法,固然code
看這個blog
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)it
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 }
世界之大無奇不有(。-`ω´-)數論是個可怕的東西