在编程竞赛中,如果要求确定某一范围内的素数,一个常用的方法是使用素数筛。在使用素数筛的过程中,需要记录某个数是否是素数,常见的方法是使用一个整数数组来记录,例如:
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
x 在 B[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 所对应的二进制位值。