【算法总结】素数
素数即只能被自身和 1 整除的大于1的正整数。
一、素数判定
怎样确定一个数是素数?我们可以用所有大于 1 小于其本身的整数去试着整除该数,若在该区间内存在某个数能整除该数则该数不是素数;若这些数都不能整除它,则该数为素数。这一朴素的算法思想时间复杂度为 O(n),n 为我们要测试的数字。但其实,我们并不用测试到 n-1 为止,我们只需测试到不比 sqrt(n)(对 n 开根号)大的整数即可,若到这个整数为止,所有正整数数均不能整除 n,则可以断定,n 为素数。
模板
bool judge(int x)//判断一个数是否为素数 { if (x <= 1)return false; int bound = (int)sqrt(x);//计算枚举上界,枚举到sqrt(x)下取整即可,因为sqrt(x)若本来就是整数,x一定不是素数 for (int i = 2; i <= bound; i++)if (x%i == 0)return false; return true; }
例4.6 素数判定
AC代码
#include<cstdio> #include<cmath> bool judge(int x)//判断一个数是否为素数 { if (x <= 1)return false; int bound = (int)sqrt(x);//计算枚举上界,枚举到sqrt(x)下取整即可,因为sqrt(x)若本来就是整数,x一定不是素数 for (int i = 2; i <= bound; i++)if (x%i == 0)return false; return true; } int main() { int x; while (scanf("%d", &x) != EOF)printf(judge(x) ? "yes" : "no"); return 0; }
#include<cstdio> #include<cmath> bool judge(int x)//判断一个数是否为素数 { if (x <= 1)return false; int bound = (int)sqrt(x) + 1;//计算枚举上界,为防止double值带来的精度损失,取根号之后再加一,宁愿多枚举一个数也不能少枚举一个 for (int i = 2; i < bound; i++)if (x%i == 0)return false; return true; } int main() { int x; while (scanf("%d", &x) != EOF)printf(judge(x) ? "yes" : "no"); return 0; }
二、素数个数
如何找出0到指定数字之间的所有素数?
如果对每一个数都如上判定的话,太过耗时,且存在大量冗余操作,由动规的“我为人人”思想,我们引入素数筛法。
素数筛法:
从 2 开始遍历 2 到 1000000 的所有整数,若当前整数没有因为它是某个小于其的素数的倍数而被标记成非素数,则判定其为素数,并标记它所有的倍数为非素数。然后继续遍历下一个数,直到遍历完 2 到 1000000 区间内所有的整数。此时,所有没被标记成非素数的数字即为我们要求的素数。
模板
数据结构
const int N = 10000;
int prime[N+5];//保存筛得的素数
int mark[N+5];//标记数组,若mark[i]为true,则其为非素数
int primeSize;//素数个数
1.init():初始化标记数组和素数个数
void init() { for (int i = 0; i <= N; i++)mark[i] = false;//初始化 primeSize = 0;//素数个数清零 }
2.Prime():素数筛法
void Prime()//素数筛法 { init(); for (int i = 2; i <= N; i++)//遍历2到N所有数字 { if (mark[i] == true)continue;//已经被标记过 prime[primeSize++] = i;//得到一个素数 for (int j = i * i; j <= N; j += i)mark[j] = true;//将其倍数标记为素数 } }
例 4.7 素数
AC代码
#include<cstdio> #include<cmath> const int N = 10000; int prime[N+5];//保存筛得的素数 int mark[N+5];//标记数组,若mark[i]为true,则其为非素数 int primeSize;//素数个数 void init() { for (int i = 0; i <= N; i++)mark[i] = false;//初始化 primeSize = 0;//素数个数清零 } void Prime()//素数筛法 { init(); for (int i = 2; i <= N; i++)//遍历2到N所有数字 { if (mark[i] == true)continue;//已经被标记过 prime[primeSize++] = i;//得到一个素数 for (int j = i * i; j <= N; j += i)mark[j] = true;//将其倍数标记为素数 } } int main() { Prime(); int n; while (scanf("%d", &n) != EOF) { bool isOutput = false; for (int i = 0; i < primeSize; i++)//依次遍历得到的所有素数 { if (prime[i] < n && prime[i] % 10 == 1)//测试当前素数是否符合条件 { if (isOutput == false)//第一个输出数字前面没有空格 { isOutput = true; printf("%d", prime[i]); } else printf(" %d", prime[i]); } } if (isOutput == false)printf("-1 ");//输出-1并换行 else printf(" "); } return 0; }
#include<iostream> using namespace std; const int N = 10000; int prime[N+5]; int mark[N+5] = {0}; int size = 0; void Prime() { for(int i=2;i<=N;i++) { if(mark[i])continue; prime[size++]=i; for(int j=i*i;j<=N;j+=i)mark[j]=1; } } int main() { Prime(); int n; while(cin>>n) { int f = 0; for(int i=0;i<size;i++) { if(prime[i]<n&&prime[i]%10==1) { if(!f) { f=1; cout<<prime[i]; } else cout<<" "<<prime[i]; } } if(!f)cout<<-1<<endl; } return 0; }
代码中的小技巧:
当我们判定 i 为素数,要标记其所有倍数为非素数时,我们并没有从 2 * i 开始标记,而是直接从 i * i 开 始标记。其原因是显然的,i * k (k < i)必已经在求得 k 的某个素因数(必小于 i)时被标记过了,即 i*k 同时也是 k 的素因数的倍数。所以这里,我们可以直接从 i 的平方开始标记起。尽可能的避免重复工作也是程序优化的一大思路。
三、分解素因数
顾名思义,对一个数分解素因数即确定素数p1,p2......pn使其满足x=p1e1 * p2e2 *......* pnen,必要时,我们还要确定e1,e2等幂指数。
例4.8 质因数的个数
解题思路
我们利用上节内容中所讲的素数筛法预先筛选出所有可能在题面所给定的数据范围内成为素因数的素数。并在程序输入待处理数字n 时,依次遍历所有小于 n 的素数,判断其是否为 n 的因数。 若确定某素数为 n 的因数,则通过试除确定其对应的幂指数。最后求出各个幂指数的和即为所求。
AC代码
#include<cstdio> int prime[100001];//保存筛得的素数 int primeSize;//保存的素数的个数 bool mark[100001];//若mark[x]为true,则表示该数x已被标记为非素数 void init() { primeSize = 0; for (int i = 2; i <= 100000; i++) { if (mark[i] == true)continue; prime[primeSize++] = i; if (i >= 1000) continue; for (int j = i * i; j <= 100000; j += i)mark[j] = true; } } int main() { init(); int n; while (scanf("%d", &n) != EOF) { int ansPrime[30];//按顺序保存分解出的素因数 int ansSize = 0;//分解出的素因数的个数 int ansNum[30];//保存分解出的素因数对应的幂指数 for (int i = 0; i < primeSize; i++)//依次测试每一个素数 { if (n%prime[i] == 0)//若该素数能整除被分解数 { ansPrime[ansSize] = prime[i];//则该素数为其素因数 ansNum[ansSize] = 0;//初始化幂指数为0 while (n%prime[i] == 0)//从被分解数中统计该素数的幂指数 { ansNum[ansSize]++; n /= prime[i]; } ansSize++;//素因数个数增加 if (n == 1)break;//若被分解数已被分解为1,则分解提前终止 } } if (n != 1)//若测试完了2到100000内所有素因数,n仍未被分解为1,剩余的因数一定是一个大于100000的素因数 { ansPrime[ansSize] = n;//记录该大素因数 ansNum[ansSize++] = 1;//该大素因数的幂指数只能为1 } int ans = 0; for (int i = 0; i < ansSize; i++)ans += ansNum[i];//统计各个素因数的幂指数 printf("%d ", ans);//输出 } return 0; }