【题目链接】
【题目考点】
1. 普通筛法找质数(埃拉托色尼筛法)
基本思想:把从2到N的一组正整数从小到大按顺序排列。从中依次删除2的倍数、3的倍数、5的倍数,直到
N
\sqrt{N}
N的倍数为止,剩余的即为2~N之间的所有质数。
普通筛法复杂度为:
O
(
n
l
o
g
l
o
g
n
)
O(nloglogn)
O(nloglogn)
为什么直到 N \sqrt{N} N的倍数为止,以下是证明:
- 命题:对于整数n满足 N < n ≤ N \sqrt{N} < n \le N N<n≤N,n是质数,或n是合数时,存在一个整数a满足 a ≤ N a \le \sqrt{N} a≤N,a是n的因数。
- 证明:显然n可以是质数。如果n是合数,使用反证法。
- 假设n是合数时,n的因数(除了1)都大于 N \sqrt{N} N。由于n是合数,n至少有两个不为1或n的因数,设为a,b,且存在等式 a ⋅ b ⋅ x = n a\cdot b\cdot x = n a⋅b⋅x=n,其中x是某个正整数。
有 a > N , b > N ⇒ a ⋅ b > N ≥ n a>\sqrt{N}, b>\sqrt{N} \Rightarrow a\cdot b >N\ge n a>N,b>N⇒a⋅b>N≥n,即 a ⋅ b > n a\cdot b > n a⋅b>n
根据 a ⋅ b ⋅ x = n a\cdot b\cdot x = n a⋅b⋅x=n,有 a ⋅ b ≤ n a\cdot b \le n a⋅b≤n
出现矛盾,因而假设不成立,原命题得证。
2. 线性筛(欧拉筛)
基本思想:让每个合数只通过它最小的质因数被筛掉。
例:
8的最小质因数是2,那么让合数8通过质数2被筛掉。
15的最小质因数是3,那么让合数15通过质数3被筛掉。
普通筛法中,数字可能会被多次筛掉,线性筛中,每个数字只被筛掉一次。
线性筛法复杂度为:
O
(
n
)
O(n)
O(n)
线性筛的字面意思就是该算法的复杂度为线性的。
例:对于合数15
在普通筛法中:看质数3的倍数时,15被筛掉一次,看质数5的倍数时,15被筛掉一次。
在线性筛中: 15只会通过质数3被筛掉一次。
线性筛法中,维护一个质数数组prime[]。
算法如下:
- 每次循环观察一个数字i
- 如果i是质数,加入质数数组
- 无论i是不是质数,都顺序遍历质数数组,取到的质数为prime[j]
- 只要i不是prime[j]的倍数,那么prime[j]就是i*prime[j]的最小质因数。i*prime[j]这个合数通过prime[j]这个最小质因数被筛掉了。
- 如果i是prime[j]的倍数,则跳出循环
原理解释如下:
- 由于prime数组中的质数是递增的,在遍历prime数组的过程中,只要没有遇到i是prime[j]的倍数的情况,那么prime[j]一定是i*prime[j]的最小质因数。当第一次遇到i是prime[j]的倍数的情况时,prime[j]仍然是i*prime[j]的最小质因数。
- 如果继续看prime[j]后面第x个质数(x大于等于1),此时要判断数字Y=i*prime[j+x]是否要被筛掉。由于prime[j+x] > prime[j],i已经具有更小的质因数prime[j],所以数字Y的最小质因数是prime[j]而不是prime[j+x],所以Y这个数不应该在i为这个值时被筛掉,它应该在i为Y/prime[j]时被筛掉。
例:假设i是25。25不是2的倍数,那么2是2*25的最小质因数,25也不是3的倍数,那么3是3*25的最小质因数,25是5的倍数,5仍然是5*25的最小质因数。
25不是7的倍数,7不是7*25的最小质因数了。7*25=175被筛掉的时机是:当i循环到35时,175=35*5,5是175的最小质因数,175通过质数5被筛掉。
【题解代码】
解法1:普通筛法(埃拉托色尼筛法)
#include<bits/stdc++.h>
using namespace std;
int main()
{
bool isPrime[1005] = {};//isPrime[i]:i是否是质数
int n;
cin >> n;
for(int i = 2; i <= n; ++i)//初值状态下,把每个数字都标记为质数
isPrime[i] = true;//isPrime[0],与isPrime[1]都为false,0,1都不是质数
for(int i = 2; i <= int(sqrt(n)); ++i)
{
if(isPrime[i])
{
for(int j = 2*i; j <= n; j += i)//如果一个数字是某质数的倍数,那么把它标记为合数
isPrime[j] = false;
}
}
for(int i = 2; i <= n; ++i)
{
if(isPrime[i])
cout << i << endl;
}
return 0;
}
解法2:线性筛(欧拉筛)
#include<bits/stdc++.h>
using namespace std;
int main()
{
bool isPrime[1005] = {};//isPrime[i]:i是否是质数
int n, prime[1005], psize = 0;//prime:质数数组 psize:prime数组长度
cin >> n;
for(int i = 2; i <= n; ++i)//初值状态下,把每个数字都标记为质数
isPrime[i] = true;//isPrime[0],与isPrime[1]都为false,0,1都不是质数
for(int i = 2; i <= n; ++i)
{
if(isPrime[i])//如果i是质数
prime[++psize] = i;//将i填充到指数数组prime
for(int j = 1; j <= psize && i*prime[j] <= n; ++j)//遍历质数数组
{//筛去当前观察的数i与某质数prime[j]的乘积
isPrime[i*prime[j]] = false;//i*prime[j]这个数通过prime[j]筛掉
if(i%prime[j] == 0)
break;
}
}
for(int i = 2; i <= n; ++i)
{
if(isPrime[i])
cout << i << endl;
}
return 0;
}