前言:好多学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 }
世界之大无奇不有(。-`ω´-)数论是个可怕的东西