深入理解线性质数(素数)筛

预备知识
首先不管是什么方法的筛质数,其最基本的思想都是一样的:假设P是任意一个数,如果这个数没有被2~P-1之间的任何数筛掉,那么它就是质数
算术基本定理可表述为:任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积(取自百度)

int p[N], cnt; //p[N]用来存质数,cnt表示质数的个数
bool st[N]; //表示当前的数有没有被筛过
朴素筛法 O ( n l o g n ) O(nlogn)
void get_primes(int n)
{
	for(int i = 2; i <= n; i ++)
	//对于每一个i,从i~n中找到i的每一个倍数并筛掉
		if(!st[i]) p[cnt ++] = i; // 如果没有被筛掉,那么就是质数
		for(int j = i + i; j <= n; j ++)
			st[j] = true;
}

由于当运行到 i 时可以确保已经用小于 i 的每一个质数都筛过一遍,因此可以保证每一个合数都会被筛掉

埃式筛法 O ( n l o g l o g n ) O(nloglogn)

在朴素筛法中,2 * 4 与 4 * 2 的结果都是8,因此一个数可以用合数筛的时候,其实已经被质数已经筛过一次,所以可以只在 i 是素数的时候筛

void get_primes(int n)
{
	for(int i = 2; i <= n; i ++)
		if(!st[i])
		{
			p[cnt ++] = i;
			for(int j = i + i; j <= n; j ++)
				st[j] = true;
		}
}

由算术基本定理知一定可以将每一个合数都筛掉(因为每一个合数都能分成若干个质数)

线性筛法 O ( n ) O(n) , n = 1e7时基本上就比埃式筛法快一倍

算法核心:保证每个合数只会被其最小质因子筛掉

void get_primes(int n)
{
	for(int i = 2; i <= n; i ++)
		if(!st[i]) p[cnt ++] = i;
		for(int j = 0; p[j] * i <= n; j ++)
		{
			st[p[j] * i] = true;
			if(i % p[j] == 0) break;
		}
}

首先证明合数是不是一定会被筛掉
假设N是一个合数,且P是N的最小质因子
N = P N P 故有N = P * \frac{N}{P}
只需要满足P小于 N P \frac{N}{P} ,当 i 等于 N P \frac{N}{P} 时,N就会被筛掉
反证法,假设 N P \frac{N}{P} 小于P
如果 N P \frac{N}{P} 是质数,那么 N P \frac{N}{P} 就是N的最小质因子,故 N P \frac{N}{P} 小于P不成立
如果 N P \frac{N}{P} 是合数,那么由算术基本定理, N P \frac{N}{P} 一定可以分出一个比P小的素数来作为N的最小质因子,故 N P \frac{N}{P} 小于P不成立
综上,P一定小于 N P \frac{N}{P} ,故每一个合数肯定会被筛掉

其次证明是不是每个合数只会被其最小质因子筛掉

for(int j = 0; p[j] * i <= n; j ++)
{
	st[p[j] * i] = true;
	if(i % p[j] == 0) break;
}

如果 i % p[ j ] == 0 说明p[ j ]是 i 的最小质因子(因为p[ j ]是从小到大枚举的), 所以p[ j ]也是 i * p[ j ]的最小质因子
如果 i % p[ j ] != 0 说明p[ j ]小于 i 的所有质因子(因为p[ j ]是从小到大枚举的),所以p[ j ]也是 i * p[ j ]的最小质因子
而至于为什么要break,因为一旦 i % p[ j ] 为0时,说明p[ j ]是 i 的最小质因子,而此时如果继续循环下去,用p[ j + 1 ] * i 筛时,由于 i 的最小质因子p[ j ]小于p[ j + 1],因此这时候被筛掉的合数就不是被他的最小质因子筛掉的了
所以break也是保证线性筛法时间复杂度的一个重要原因~

发布了1 篇原创文章 · 获赞 7 · 访问量 111

猜你喜欢

转载自blog.csdn.net/weixin_43819867/article/details/105221137