【笔记】 素数筛法(朴素筛法,优化筛法,线性筛法)

素数在OI中的应用很广

很多题都会用到素数,那么如何得到素数就是件很重要的事了

暴力枚举并判断?当然可以但实在太暴力了一点

考虑到如果一个数i是素数,那么i的倍数显然不可能是素数

由此我们可以通过不断的筛去不是素数的数,从而得到剩下的素数

----------------------------------------------------------------------------------------------

算法1.朴素算法:

假设我们要得到1~10中的素数

那么首先我们已知1不是素数,最小的素数为2,先列出一个1~10的数组

1 2 3 4 5 6 7 8 9 10

直接将1划去得到:

1 2 3 4 5 6 7 8 9 10

从2开始筛去它的倍数:

1 2 3 4 5 6 7 8 9 10

然后我们向后接着找是素数的数,继续以3来筛去它的倍数:

1 2 3 4 5 6 7 8 9 10

枚举到4发现它不是素数,直接跳过,一直枚举到10为止,我们就得到了一个有标记的数组

1 2 3 4 5 6 7 8 9 10

最后剩下的数(即标红的数)就是我们所要的素数了

从这一个例子开始,我们可以轻松地推广至求1~n的素数

那么只需从2开始枚举至n并删去素数的倍数就可以了

计算量为n/2+n/3+n/4+...,是一个调和级数,时间复杂度为O(nlogn)

代码如下:

void is_prime (int n)
{
	memset (vis, 0, sizeof (vis));
	for (int i = 2; i <= n; ++i)
		for (int j = i + i; j <= n; j += i)
			vis[j] = 1; //vis[]用来判断是否为素数,打上标记的都为合数 
}

---------------------------------------------------------------------------------------------------------------------------------

算法2.优化算法:

我们注意到朴素算法中比如36会被2,3,4,6,9,12,18这6个数筛到,相当于被2*18,3*12,4*9,6*6,9*4,12*3,18*2筛到,实际上

a*b和b*a是等价的,于是,我们可以通过枚举小的那个因子,来优化一半的计算量,复杂度不变为O(nlogn),常数上优化了

代码如下:

void is_prime (int n)
{
	int up = (double) sqrt(n*1.0) + 1;
	for (int i = 2; i <= up; ++i)
		for (int j = i * i; j <= n; j += i)
			vis[j] = 1;
}

---------------------------------------------------------------------------------------------------------------------------------

算法3.线性筛法:

虽然优化算法的复杂度已经不是那么大了,但我们发现,就算枚举小的因子,一个数x依旧会被1或多个数筛过,

即对一个数有重复的筛去,所以我们考虑怎么样能够让一个数只被筛去一次呢?

那么便是我们的线性筛法,我们考虑筛去一个数时,只由它的最小素因子来进行,就可以做到O(n)的筛素数

代码如下:

void is_prime (int n)
{
	for (int i = 2; i <= n; ++i)
	{
		if (!vis[i])
			p[++cnt] = i;
		for (int j = 1; j <= cnt; ++j)
		{
			if (i * p[j] > n)
				break;
			vis[i * p[j]] = 1; //p[j]是i*p[j]的最小素因子 
			if (i % p[j] == 0) //再往后p[j]就不是最小素因子了 
				break;
		}
	}
}

猜你喜欢

转载自blog.csdn.net/El_Apocalipsis/article/details/80995791