线性筛素数
Posted jcrl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线性筛素数相关的知识,希望对你有一定的参考价值。
想要快速地筛出一定上限内的素数?
下面这种方法可以保证范围内的每个合数都被删掉(在 bool 数组里面标记为非素数),而且任一合数只被:
“最小质因数 × 最大因数(非自己) = 这个合数”
的途径删掉。由于每个数只被筛一次,时间复杂度为 O(n)O(n)O(n)。
欧拉筛
先浏览如何实现再讲其中的原理。
实现
bool isPrime[1000001];
//isPrime[i] == 1表示:i是素数
int Prime[1000001], cnt = 0;
//Prime存质数
void GetPrime(int n)//筛到n
{
memset(isPrime, 1, sizeof(isPrime));
//以“全都是素数”为初始状态,逐个删去
isPrime[1] = 0;//1不是素数
for(int i = 2; i <= n; i++)
{
if(isPrime[i])//没筛掉
Prime[++cnt] = i; //i成为下一个素数
for(int j = 1; j <= cnt && i*Prime[j] <= n/*不超上限*/; j++)
{
//从Prime[1],即最小质数2开始,逐个枚举已知的质数,并期望Prime[j]是(i*Prime[j])的最小质因数
//当然,i肯定比Prime[j]大,因为Prime[j]是在i之前得出的
isPrime[ i*Prime[j] ] = 0;
if(i % Prime[j] == 0)//i中也含有Prime[j]这个因子
break; //重要,见原理
}
}
}
原理概述
(1)一个数的最小因数(非1)当然是一个质数。毕竟如果是合数的话可以再分。
(2)代码中 PrimePrimePrime 数组中存的质数是递增的。
(3)代码中的 i×Prime[j]i×Prime[j]i×Prime[j] 是尝试筛掉的某个合数,其中,我们期望 Prime[j]Prime[j]Prime[j] 是这个合数的最小质因数 (1)(1)(1)。它是怎么得到保证的?
jjj 的循环中,有一句就做到了这一点:
if(i % Prime[j] == 0)
break;
jjj 的循环到这里停止的理由是:
-
iii 的最小质因数是 Prime[j]Prime[j]Prime[j](否则更早break)。
-
i×Prime[<j]i × Prime[<j]i×Prime[<j] 的最小质因数是 Prime[<j]Prime[<j]Prime[<j]。符合筛条件 (1)(1)(1)。
-
i×Prime[>j]i × Prime[>j]i×Prime[>j] 的最小质因数是 Prime[j]Prime[j]Prime[j]。不符筛条件 (1)(1)(1)。
(如果你立即理解了上面这三点,并且发现任何合数在整个流程中一定被最小质因数筛掉(“那个” iii 内枚举质数不会提前break),那么后面都不用看了)
可以具体说成是:
①:如果 imodPrime[j]==0i mod Prime[j] == 0imodPrime[j]==0,说明 iii 有 Prime[j]Prime[j]Prime[j] 这个因子。那么 iii 可以表示为 Prime[j]×kPrime[j] × kPrime[j]×k。
②:如果我们再往下,下一个被筛掉的合数将是 i×Prime[j+1]i × Prime[j+1]i×Prime[j+1](此时我们期望 Prime[j+1]Prime[j+1]Prime[j+1] 是这个合数的最小质因数),
③:根据①,该合数可以表示为 (Prime[j]×k)×Prime[j+1](Prime[j] × k) × Prime[j+1](Prime[j]×k)×Prime[j+1]。
④:看, Prime[j]Prime[j]Prime[j] 明显就是该合数的一个更小的质因数,说明该合数的最小质因数并不是 Prime[j+1]Prime[j+1]Prime[j+1],那么用 Prime[j+1]Prime[j+1]Prime[j+1] 去筛这个合数是不符合要求的。所以不应该在这里筛掉。
⑤:后面 i∗Prime[j+2]i * Prime[j+2]i∗Prime[j+2] 等等与此相同,他们都一定有更小的质因数 Prime[j]Prime[j]Prime[j],就不要用 Prime[j+2]Prime[j+2]Prime[j+2] 这个更大的质因数去筛。
注意,imodPrime[j]==0i mod Prime[j] == 0imodPrime[j]==0 并不影响 Prime[j]Prime[j]Prime[j] 是 i×Prime[j]i×Prime[j]i×Prime[j] 这个数的最小质因数。
小提示:
当 iii 还不大的时候,可能会一层内就筛去大量质数,看上去耗时比较大,但是由于保证了筛去的质数日后将不会再被筛,复杂度是线性的。到 iii 接近 nnn 时,每层几乎都不用做什么事。
建议看下面两个并不烦的证明,便于信任这个筛法和以后扩展学习。
正确性(所有合数都会被标记)证明
设一合数 CCC 的最小质因数是 p1p_1p1?,设 B=C/p1B = C / p_1B=C/p1?(C=B×p1C = B × p_1C=B×p1?),则 BBB 的最小质因数不小于 p1p_1p1?(否则 CCC 也有这个更小因子)。那么当枚举到 i=Bi = Bi=B 时,我们会从小到大枚举各个质数,因为 i=Bi = Bi=B 的最小质因数不小于 p1p_1p1?,所以 iii 在质数枚举至 p1p_1p1? 之前都不会break,因而就这次而言 CCC 一定会被 B×piB × p_iB×pi? 删去。
核心:亲爱的 BBB 的最小质因数必不小于 p1p_1p1?。
例:315=3×3×5×7315 = 3 × 3 × 5 × 7315=3×3×5×7,其最小质因数是 333。考虑 i=315/3=105i = 315 / 3 = 105i=315/3=105 时,我们从小到大逐个枚举质数,正是因为 iii 的最小质因数也不会小于 333(本例中就是 333),所以当枚举 j=1(Prime[j]=2)j = 1 (Prime[j] = 2)j=1(Prime[j]=2) 时,iii 不包含 222 这个因子,也就不会break,直到 Prime[j]=3Prime[j] = 3Prime[j]=3 之后才退出。
当然质数不能表示成“大于1的某数×质数”,所以整个流程中不会标记。
线性复杂度证明
注意这个算法一直使用“某数×质数”去筛合数,又已经证明一个合数一定会被它的最小质因数 p1p_1p1? 筛掉,所以我们唯一要担心的就是同一个合数是否会被“另外某数 × p1p_1p1? 以外的质数”再筛一次导致浪费时间。设要筛的合数是 CCC,设这么一个作孽的质数为 pxp_xpx?,再设 A=C/pxA = C / p_xA=C/px?,则 AAA 中有 p1p_1p1? 这个因子。当 i=Ai = Ai=A 想要重筛 CCC,却在枚举 Prime[j]=p1Prime[j] = p_1Prime[j]=p1? 时因为 imodPrime[j]==0i mod Prime[j] == 0imodPrime[j]==0 就退出了。因而 CCC 的任何 p1p_1p1? 外的质因数都不能筛它。
核心:罪恶的 AAA 中必有 p1p_1p1? 这个因子。
例:315=3×3×5×7315 = 3 × 3 × 5 × 7315=3×3×5×7。首先,虽然看上去有两个 333,但我们筛数的唯一一句话就是
isPrime[ i*Prime[j] ] = 0;
所以,315315315 只可能用 105×3105 × 3105×3 或 63×563 × 563×5 或 45×745 × 745×7 这三次筛而非四次。然后,非常抱歉,后两个 i=63,i=45i = 63, i = 45i=63,i=45 都因为贪婪地要求对象为 555 或 777 而被迫拥有 333 这个因数,因此他们根本枚举不到对象就break了。
以上两个一证,也就无可多说了。
以上是关于线性筛素数的主要内容,如果未能解决你的问题,请参考以下文章