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

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

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 来标记某个数 x x x 是否为素数,如果 x x x 不是素数,则标记 primes[x] = 1,否则 primes[x] = 0。与此同时,为了节省存储空间,筛选得到的素数也存放在 p r i m e s primes primes 中,因为不是所有整数都是素数,因此这样的做法没有问题。

可以看到,使用上述的方法来标记是否为素数,与所求素数的范围有关,由于 32 32 32 位整数占 4 4 4 字节存储空间,故所需的存储空间为: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;
	}
}

由于从 1 1 1 10000010 10000010 10000010 之间,素数的个数不超过 70 70 70 万个,这样空间花费约为 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 通过位运算判断序号为 i i i 的位是否为 1 1 1 来确定 flag.test(i) 的值。那么我们可以进一步地予以简化,因为在素数筛中,只应用了 bitset 的判断和标记两项功能,其他功能并未应用。

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


const int MAXB = 100000010;

int B[MAXB >> 5];

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

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

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

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页