回复读者提问:位标记在素数筛中的应用

本文介绍了在编程竞赛中如何优化素数筛算法的空间占用。从使用整数数组记录素数状态开始,逐步优化到使用位操作减少存储空间,并详细解释了如何通过位映射来高效标记素数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在编程竞赛中,如果要求确定某一范围内的素数,一个常用的方法是使用素数筛。在使用素数筛的过程中,需要记录某个数是否是素数,常见的方法是使用一个整数数组来记录,例如:

const int MAXN = 10000010;

int primes[MAXN], cnt;

void sieve()
{
	for (int i = 2; i < MAXN; i++)
	{
		if (primes[i]) continue;
		primes[cnt++] = i;
		for (int j = i + i; j < MAXN; j += i)
			primes[j] = 1;
	}
}

在上述代码中,使用整数数组 primes 来标记某个数 xxx 是否为素数,如果 xxx 不是素数,则标记 primes[x] = 1,否则 primes[x] = 0。与此同时,为了节省存储空间,筛选得到的素数也存放在 primesprimesprimes 中,因为不是所有整数都是素数,因此这样的做法没有问题。

可以看到,使用上述的方法来标记是否为素数,与所求素数的范围有关,由于 323232 位整数占 444 字节存储空间,故所需的存储空间为:MAXN * 4 字节。

可以将标记数组换成 bool 类型的数组,比如:

const int MAXN = 10000010;

bool flag[MAXN];
int primes[700000], cnt;

void sieve()
{
	for (int i = 2; i < MAXN; i++)
	{
		if (flag[i]) continue;
		primes[cnt++] = i;
		for (int j = i + i; j < MAXN; j += i)
			flag[j] = 1;
	}
}

由于从 111100000101000001010000010 之间,素数的个数不超过 707070 万个,这样空间花费约为 MAXN 字节(当 MAXN 较大时,存储素数所使用的空间基本可以忽略不计)。

进一步地,可以使用位来标记某个整数是否为素数。例如,利用 C++⁡\operatorname{C++}C++ 提供的 bitset 序列容器:

const int MAXN = 10000010;

bitset<MAXN> flag;
int primes[700000], cnt;

void sieve()
{
	for (int i = 2; i < MAXN; i++)
	{
		if (flag.test(i)) continue;
		primes[cnt++] = i;
		for (int j = i + i; j < MAXN; j += i)
			flag.set(j);
	}
}

bitset 在内部实现中是采用整数数组的方式,整数数组可以视为一长串的位组合,bitset 通过位运算判断序号为 iii 的位是否为 111 来确定 flag.test(i) 的值。那么我们可以进一步地予以简化,因为在素数筛中,只应用了 bitset 的判断和标记两项功能,其他功能并未应用。

要简化 bitset 的功能,关键是将序号 xxx 映射为整数数组中某个元素的某个特定位。举个例子,一个整数数组中,每个元素都是一个整数,占 444 个字节,即一个数组元素有 323232 个位,可以标记 323232 个整数。


const int MAXB = 100000010;

int B[MAXB >> 5];

也就是说,B[0]B[0]B[0] 中的 323232 个二进制位可以用于标记整数 000313131 是否为素数,B[1]B[1]B[1] 中的 323232 个二进制位可以用于标记整数 323232636363 是否为素数……依此类推。为了方便的将序号 xxx 映射到整数数组 BBB 中的某个位,定义以下的两个宏:

#define GET(x) (B[x >> 5] & (1 << (x & 0x1F)))
#define SET(x) (B[x >> 5] |= (1 << (x & 0x1F)))

由于每 323232 个整数对应 BBB 中的一个元素,因此整数 xxx 所对应的 BBB 中的元素为 B[x / 32],即 B[x >> 5],接下来需要确定 xxxB[x >> 5] 对应着哪一个二进制位。由于是每 323232 个整数“一段”,整数 xxx 在第 x/32x / 32x/32 段,xxx 在段内的序号就是 xxx 除以 323232 后所得的余数,使用位运算来表示的话,就是 xxx 对应二进制数的最后 555 个二进制位的值,因此,x & 0x1F 表示整数 xxx 在段内的序号,结合获取单个二进制位值的方法,容易理解 (B[x >> 5] & (1 << (x & 0x1F))) 就可以获取 xxx 所对应的二进制位值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值