线性筛的理解及应用

素数筛法

如果我们想要知道小于等于 $n$ 有多少个素数呢?

一个自然的想法是我们对于小于等于 $n$  的每个数进行一次判定。这种暴力的做法显然不能达到最优复杂度,考虑如何优化。

考虑这样一件事情:如果  是合数,那么  的倍数也一定是合数。利用这个结论,我们可以避免很多次不必要的检测。

如果我们从小到大考虑每个数,然后同时把当前这个数的所有(比自己大的)倍数记为合数,那么运行结束的时候没有被标记的数就是素数了。

int Eratosthenes(int n) {
  int p = 0;
  for (int i = 0; i <= n; ++i) is_prime[i] = 1;
  is_prime[0] = is_prime[1] = 0;
  for (int i = 2; i <= n; ++i) {
    if (is_prime[i]) {
      prime[p++] = i;  // prime[p]是i,后置自增运算代表当前素数数量
      for (int j = i * i; j <= n;
           j += i)  // 因为从 2 到 i - 1 的倍数我们之前筛过了,这里直接从 i
                    // 的倍数开始,提高了运行速度
        is_prime[j] = 0;  //是i的倍数的均不是素数
    }
  }
  return p;
}

以上为 Eratosthenes 筛法 (埃拉托斯特尼筛法),时间复杂度是 $O(nloglogn)$.

以上做法仍有优化空间,我们发现这里面似乎会对某些数标记了很多次其为合数。有没有什么办法省掉无意义的步骤呢?

答案当然是:有!

如果能让每个合数都只被标记一次,那么时间复杂度就可以降到 $O(n)$了。

//这里再求素数的同时也计算了 $phi$ ,是为了看出素数筛与线性筛的联系。

void init() {
  phi[1] = 1;
  for (int i = 2; i < MAXN; ++i) {
    if (!vis[i]) {
      phi[i] = i - 1;
      pri[cnt++] = i;
    }
    for (int j = 0; j < cnt; ++j) {
      if (1ll * i * pri[j] >= MAXN) break;
      vis[i * pri[j]] = 1;
      if (i % pri[j]) {
        phi[i * pri[j]] = phi[i] * (pri[j] - 1);
      } else {
        // i % pri[j] == 0
        // 换言之,i 之前被 pri[j] 筛过了
        // 由于 pri 里面质数是从小到大的,所以 i 乘上其他的质数的结果一定会被
        // pri[j] 的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break
        // 掉就好了
        phi[i * pri[j]] = phi[i] * pri[j];
        break;
      }
    }
  }
}

关键之处在:if(i%prime[j]==0)  break;

这句代码保证了每个数最多被筛一次,将时间复杂度降到了线性。

证:prime[]数组中的素数是递增的,当i能整除prime[j],那么i*prime[j+1]这个合数肯定会被prime[j]乘以某个数筛掉。因此,这里直接break掉,将i*prime[j+1]及之后的给后面的数去筛。这种方法能保证每个数只被筛一遍,又能保证每个数都被筛到。

为了更好的理解,画出前面几次筛的情况:

线性筛的理解及应用

 从图上我们看到,第一列筛掉的是最小素因子是2的数,第二列筛掉的是最小素因子为3的数,依次类推,可以把所有的合数都筛掉。

因为是按照最小素因子筛选,每个数的最小素因数只有一个,所以可以保证每个数都只会被筛一遍。

例如,$i=6$ 时,第一个素数是2,能整除,筛掉12后就break;至于第二个素数3,6x3中的最小素因数肯定是前一个素数2,所以它要到 $i=9$,素数取2时才被筛掉。

上面的这种 线性筛法 也称为 Euler 筛法 (欧拉筛法)。

欧拉筛的速度大概是埃氏的3~4倍,然而在小数据中却有被完爆的可能(因为埃氏筛cache友好?)。

 

 

1.https://www.jianshu.com/p/f16d318efe9b

2. http://www.voidcn.com/article/p-uetmmxng-bha.html

 

上一篇:【优先队列】Function


下一篇:Codeforces 1178D. Prime Graph