从埃氏筛到线性筛

埃拉托斯特尼筛法(埃氏筛)

小学的时候,老师就讲过,质数是因数只有 $1$ 和它自己的数字,于是我们很容易推出,如果一个数是质数,那么它的倍数一定是合数。

于是

  • 我们从 $2$ 开始,如果当前的数字 $i$ 没有被标记为合数,那么他就是质数。
  • 然后把 $i$ 的所有倍数都标记为合数。
  • 继续下一个数。

我们就可以得到这样一个程序:

vector<int> primes;
vector<bool> vis(maxn + 1);

void init() {
	primes.push_back(1);	//把1放进去用于构造1-base索引,部分情况下也需要用到“第0个质数是1”
	for (int i = 2; i <= maxn; ++i) {
		if (!vis[i]) {
			primes.push_back(i);
			for (int j = i + i; j <= maxn; j += i) {
				vis[j] = 1;
			}
		}
	}
}

时间复杂度为 $O(nloglogn)$ 。

欧拉筛(线性筛)

虽然埃氏筛的时间复杂度只比线性多了一些,但是仍有被卡常的风险,于是我们使用线性时间复杂度的欧拉筛。

根据上面的描述我们知道,埃氏筛中一个数字会被它的所有质因数标记,要想优化掉重复标记的时间,我们需要保证每个合数都只被它最小的那个质因数标记。

于是:

  • 对于每个 $i$ ,如果 $i$ 没有被标记,那么它就是质数。
  • 遍历所有已经生成的质数
    • 标记 $p*i$ 为合数
    • 如果 $i$ 能被 $p$ 整除,就break 。
vector<int> primes, spf(maxn + 1);

void init() {
	primes.push_back(1);	//把1放进去用于构造1-base索引,部分情况下也需要用到“第0个质数是1”
	for (int i = 2; i <= maxn; ++i) {
		if (!vis[i]) primes.push_back(i);
		for (int j = 1; primes[j]*i <= maxn; ++j) {
			int m = primes[j] * i;
			spf[i] = primes[j];
			if (i % primes[j] == 0) break;
		}
	}
}

那为什么 break 能保证线性。我们分两种情况讨论:

  • $p < spf[i]$
    • 那么 $p$ 比 $i$ 的最小质因子要小。
    • 说明 $p$ 是 $i*p$ 的最小质因子,我们要标记它。
  • $p == spf[i]$
    • 那么 $i*p$ 的最小质因子还是 $p$ 。
    • 我们标记完 $i*p$ 后,需要停止找更大的质数,如果我们使用了更大的质数 $q$ ,因为 $spf[i]==p$ ,所以 $i*q$ 的最小质因子还是 $p$ ,就会发生重复标记。

所以我们需要 $i%p==0$ 就 $break$ 。

时间复杂度为 O(n) 。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇