筛质/素数

筛质数

质数也称素数。依整除性定义:素数只能被常数1或自己整除,不能被常数1或自己以外的其他数整除,这种正整数称为素数。

与之相对的是合数,合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。

要求:筛选出n以内的所有质数。

埃氏筛

把每个数的倍数标记,被标记的数显然是合数。

没有被标记的就是质数。因为对于一个数n,他没有被标记,说明他不是2n-1间任何数的倍数,所以他是质数。

代码1

朴素筛,对每一个数筛除其倍数。

cnt = 0;  // 质数的数量
for(int i = 2; i <= n; i ++) {
	if(!a[i])  // a[i] = false 代表i是质数
		primes[cnt++] = i;
	for(int j = i + i; j <= n; j += i)   // 2i, 3i, ...
		a[j] = true;   // a[j] = true 代表数j被标记,即不是质数
}

代码在i=2时循环筛 n 2 \frac{n}{2} 2n次,i=3时循环筛 n 3 \frac{n}{3} 3n次…即 n 2 + n 3 + . . . + n n = n ( 1 2 + 1 3 + . . . + 1 n ) \frac{n}{2}+\frac{n}{3}+...+\frac{n}{n} =n(\frac{1}{2}+\frac{1}{3}+...+\frac{1}{n}) 2n+3n+...+nn=n(21+31+...+n1)。括号内为调和级数,约为ln n + C。即代码1时间复杂度约为O(n ln n) < O(n log n)

代码2

只需要对质数筛倍数,因为合数已经被质数标记过了。

根据整数唯一分解定理,任何一个大于1的整数n都可以分解成若干个素因数的连乘积,如果不计各个素因数的顺序,那么这种分解是唯一的。

也就是说,对合数x来说,他会被质数筛掉;同理,对于x的倍数来说,他也会被质数筛掉。

所以不需要对合数筛其倍数。

for(int i = 2; i <= n; i ++) {
	if(!a[i]) {
		primes[cnt++] = i;
		for(int j = i + i; j <= n; j += i)   // 2i, 3i, ...
			a[j] = true;
	}
}

根据质数定理,1~n中有 n l n   n \frac{n}{ln \ n} ln nn个质数。对每个质数x,需要筛n/x次,总复杂度约为 n l n   n × l n   n = n \frac{n}{ln \ n} \times ln \ n = n ln nn×ln n=n,实际为O(n log log n)。

代码3

上述代码2中核心代码可以再优化一些些,因为他是从i*2开始枚举,会有一些重复计算。

for(int j = i + i; j <= n; j += i)   // i*2, i*3, ...
    a[j] = true;

2x2 2x3 2x4 2x5 2x6
3x2 3x3 3x4 3x5 3x6    // 3x2 在 上一行 2x3 中已经算过了
5x2 5x3 5x4 5x5 5x6    // 5x2 和 5x3 在上两行已经算过了
...

所以,改成从i*i开始枚举即可。

// 注意尽量不要写成 i * j <= n ,因为i * j可能因为过大而越界为负数,导致下标错误
for(int j = i; i <= n / j; j ++)   // i*i, i*(i+1), ...
    a[i * j] = true;

线性筛

代码2中会有重复筛,例如质数2和3都会筛6,12,…。

线性筛中,数n只会被最小质因子筛一次,时间复杂度约为O(n)

代码

从小到大枚举所有的质数。

i % primes[j] == 0primes[j]一定是i的最小质因子,primes[j]也一定是primes[j]*i的最小质因子。

i % primes[j] != 0primes[j]一定小于i的所有质因子,primes[j]也一定是primes[j]*i的最小质因子。

不需要j < cnt && primes[j] * i <= n 。因为如果i是合数,当primes[j]i的最小质因子时会结束;如果i是质数,primes[j] == i时也会结束。

if(!a[i])
	primes[cnt++] = i;
for(int j = 0; primes[j] * i <= n; j ++) {
	a[primes[j] * i] = true;
	if(i % primes[j] == 0) 
		break;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值