素数筛

埃式筛法

\(Eratosthenes\) 筛法

埃拉托色尼选筛法,是古希腊数学家埃拉托色尼提出的一种筛选法。

该筛法基于这样的想法:任意大于1的正整数 \(x\) 的倍数 \(2x, 3x,...\) 都不是质数。根据质数的定义,上述命题显然成立。

从 2 开始,由小到达扫描每个 x ,把它的倍数 \(2x,3x,\cdots, \lfloor N/x\rfloor*x\) 标记为合数。

每当扫描到一个树时,若它尚未被标记,则它不能被\(2\sim x-1\) 之间的任何数整除,该数就是质数。

$Eratosthenes $ 筛法过程如下:

image-20200819110140929

另外我们可以发现,2 和 3 都会把 6 标记为合数。实际上,小于 \(x^2\) 的 x 的倍数在扫描更小的数时就已经被标记过了。因此,我们可以对 \(Eratosthenes\) 筛法进行优化,只需要从 \(x^2\) 开始把 x 的倍数标记为 合数即可。

int prime[N], v[N], m;
void primes(int n){
    memset(v, 0, sizeof v);
    for(int i=2;i<=n;i++){
        if(v[i] == 0){
            prime[++m] = i;
        }
	    for(int j=i;j<=n/i;j++) v[i*j] = 1;
    }
}

该算法复杂度为\(O(\sum_{质数p\le N}\frac{N}{p}) = O(N\log \log N)\) 。效率已经非常接近线性,是最常用的质数筛法

线性筛法

Eratosthenes 筛法利用的原理是 任意大于一的正整数 x 的倍数 2x,3x,... 等都不是质数

但是即便如此也会有重复标记的现象,例如12既会被2又会被3标记,在标记2的倍数时,\(12 = 6*2\),在标记3的倍数时,\(12 = 4*3\) ,根本原因是没有找到唯一产生12的方式。

线性筛法的核心原理

每个合数必有一个最大因子(不包括它本身),用这个因子把合数筛掉

换言之:每个合数必有一个最小素因子,用这个因数把合数筛掉

过程

假设对于一个确定的整数 \(i\)\(i\) 是一个合数 \(t\) 的最大因数,\(t\) 显然可能不唯一(例如 30 和 45 最大因数都是 15)。但是仔细想一想,必然有一个p,满足:

\[t = i * p ~~ (p\le i ,p是质数) \]

  • \(p\)为什么一定小于等于 \(i\)?因为 \(i\)\(t\) 的最大因数。
  • 为什么 \(p\) 一定是质数?因为如果 \(p\) 是合数,那么 \(i\) 就一定不是 \(t\) 的最大因数,因为 \(p\) 可以再拆成若干素数相乘,这些素数再与 \(i\) 相乘会使该因数更大。

既然如此,我们只需要把所有小于 \(i\) 的质数 \(p\) 都挨个乘一次拿到所有合数就好了。可是,这样就不会有重复标记嘛?

会的,我们一不小心就忘记了最初的条件。我们要满足 \(i\)\(t\) 的最大因数。如果 \(p\) 大于 \(i\) 的最小质因数,那 \(i\) 还是 \(t\) 的最大因数嘛?显然不是,任何一个合数 \(t\) 都能唯一分解为有限个质数的乘积,除去这其中最小的质因数,其他的都乘起来就是最大因数 \(i\) 。所以我们不能让 \(p\) 大于 \(i\) 的最小质因数(设为\(x\)),否则 \(i\) 将不再是 \(t = i*p\) 的最大因数,其最大因数应该是\(i*p/x\)

下面有两个版本,核心处理稍有一点点不同,理解即可。

版本一

  • v[i] 表示 i 的最小质因数。如果i就是质数,那么v[i] = i
  • prime[j] 表示第 j 个质数。与之前的筛法不同,这个数组是存放质数的,而不是标记质数的
#define MAXN 1000012
int prime[MAXN],v[MAXN];
int m=0;//m表示现在筛出m个质数
void primes(int n)
{
      for(int i=2;i<=n;i++)
      {
		if(v[i]==0)//如果v[i]为0,说明 i 之前没有被筛到过,i 为质数
		{
			v[i] = i;
                      prime[++m] = i;
		}
		for(int j = 1;j<=m;j++)//遍历小于 i 的所有质数
		{
                      //如果质数大于 i 的最小质因数或者乘起来大于n就跳出循环
			if(prime[j] > v[i] || prime[j] > n/i) break;
			v[i*prime[j]] = prime[j];//标记 i*prime[j] 的最小质因数是prime[j]
		}
      }
}

版本二

  • v[i] i 为质数则为0,否则为 1
  • prime[j] 与上面相同
#define MAXN 1000000
int prime[MAXN],v[MAXN];
int m=0;//m表示现在筛出m个质数
void primes(int n)
{
      v[1] = 1;//1不是质数,提前处理
      for(int i=2;i<=n;i++)
      {
            if(v[i]==0)//如果v[i]为0,说明 i 之前没有被筛到过,i 为质数
            prime[++m] = i;
            for(int j = 1;j<=m;j++)//遍历小于 i 的所有质数
            {
                  //乘起来大于就跳出循环
                  if(prime[j] > n/i) break;
                  v[i*prime[j]] = 1;//标记 i*prime[j] 的最小质因数是prime[j]
                  //当遇到最小的质数是i的因数时,break
                  if(i%prime[j]==0)break;
            }
      }
}
posted @ 2018-10-21 11:57  kpole  阅读(2692)  评论(6编辑  收藏  举报