回覆讀者提問 位標記在素數篩中的應用

2021-10-10 22:36:14 字數 2750 閱讀 9020

在程式設計競賽中,如果要求確定某一範圍內的素數,乙個常用的方法是使用素數篩。在使用素數篩的過程中,需要記錄某個數是否是素數,常見的方法是使用乙個整數陣列來記錄,例如:

const

int maxn =

10000010

;int primes[maxn]

, cnt;

void

sieve()

}

在上述**中,使用整數陣列primes來標記某個數 x

xx 是否為素數,如果 x

xx 不是素數,則標記primes[x] = 1,否則primes[x] = 0。與此同時,為了節省儲存空間,篩選得到的素數也存放在 pri

me

sprimes

primes

中,因為不是所有整數都是素數,因此這樣的做法沒有問題。

可以看到,使用上述的方法來標記是否為素數,與所求素數的範圍有關,由於 32

3232

位整數佔 4

44 位元組儲存空間,故所需的儲存空間為:maxn * 4位元組。

可以將標記陣列換成bool型別的陣列,比如:

const

int maxn =

10000010

;bool flag[maxn]

;int primes[

700000

], cnt;

void

sieve()

}

由於從 1

11 到 10000010

10000010

100000

10之間,素數的個數不超過 70

7070

萬個,這樣空間花費約為maxn位元組(當maxn較大時,儲存素數所使用的空間基本可以忽略不計)。

進一步地,可以使用位來標記某個整數是否為素數。例如,利用 c++

⁡\operatorname

c++ 提供的bitset序列容器:

const

int maxn =

10000010

;bitset flag;

int primes[

700000

], cnt;

void

sieve()

}

bitset在內部實現中是採用整數陣列的方式,整數陣列可以視為一長串的位組合,bitset通過位運算判斷序號為 i

ii 的位是否為 1

11 來確定flag.test(i)的值。那麼我們可以進一步地予以簡化,因為在素數篩中,只應用了bitset的判斷和標記兩項功能,其他功能並未應用。

要簡化bitset的功能,關鍵是將序號 x

xx 對映為整數陣列中某個元素的某個特定位。舉個例子,乙個整數陣列中,每個元素都是乙個整數,佔 4

44 個位元組,即乙個陣列元素有 32

3232

個位,可以標記 32

3232

個整數。

const

int maxb =

100000010

;int b[maxb >>5]

;

也就是說,b[0

]b[0]

b[0]

中的 32

3232

個二進位制位可以用於標記整數 0

00 至 31

3131

是否為素數,b[1

]b[1]

b[1]

中的 32

3232

個二進位制位可以用於標記整數 32

3232

至 63

6363

是否為素數……依此類推。為了方便的將序號 x

xx 對映到整數陣列 b

bb 中的某個位,定義以下的兩個巨集:

#define get(x) (b[x >> 5] & (1 << (x & 0x1f)))

#define set(x) (b[x >> 5] |= (1 << (x & 0x1f)))

由於每 32

3232

個整數對應 b

bb 中的乙個元素,因此整數 x

xx 所對應的 b

bb 中的元素為b[x / 32],即b[x >> 5],接下來需要確定 x

xx 在b[x >> 5]對應著哪乙個二進位制位。由於是每 32

3232

個整數「一段」,整數 x

xx 在第 x/32

x / 32

x/32

段,xx

x 在段內的序號就是 x

xx 除以 32

3232

後所得的餘數,使用位運算來表示的話,就是 x

xx 對應二進位制數的最後 5

55 個二進位制位的值,因此,x & 0x1f表示整數 x

xx 在段內的序號,結合獲取單個二進位制位值的方法,容易理解(b[x >> 5] & (1 << (x & 0x1f)))就可以獲取 x

xx 所對應的二進位制位值。

對一位讀者留言的回覆,並在此重申我的原則。

一位讀者對我的翻譯提出了一些比較嚴肅的建議。當然,我也知道 忠言逆耳,良藥苦口 但是有些原則想在這裡再次重申一下。譯者你不要生氣,我只是客觀地說一下我看您的譯作的感受。如果您看過錢歌川先生的 翻譯的技巧 或者讀過任何一本大學英漢翻譯教科書,我想您的作品不至於這麼晦澀和支離破碎。翻譯是一項系統工程,您...