位運算簡介及實用技巧(三) 高階篇 2

2021-07-02 18:28:11 字數 3491 閱讀 2566

今天我們來看兩個稍微複雜一點的例子。

n皇后問題位運算版

n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行**是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim:=(1 shl n)-1。主程式呼叫test(0,0,0)後sum的值就是n皇后總的解數。拿這個去交usaco,0.3s,暴爽。

procedure test(row,ld,rd:longint);

varpos,p:longint;

begin

if row<>upperlim then

begin

pos:=upperlim and not (row or ld or rd);

while pos<>0 do

begin

p:=pos and -pos;

pos:=pos-p;

test(row+p,(ld+p)shl 1,(rd+p)shr 1);

end;

endelse inc(sum);

end;

乍一看似乎完全摸不著頭腦,實際上整個程式是非常容易理解的。這裡還是建議大家自己單步執行一**竟,實在沒研究出來再看下面的解說。

和普通演算法一樣,這是乙個遞迴過程,程式一行一行地尋找可以放皇后的地方。過程帶三個引數,row、ld和rd,分別表示在縱列和兩個對角線方向的限制條件下這一行的哪些地方不能放。我們以6x6的棋盤為例,看看程式是怎麼工作的。假設現在已經遞迴到第四層,前三層放的子已經標在左圖上了。紅色、藍色和綠色的線分別表示三個方向上有衝突的位置,位於該行上的衝突位置就用row、ld和rd中的1來表示。把它們三個並起來,得到該行所有的禁位,取反後就得到所有可以放的位置(用pos來表示)。前面說過-a相當於not a + 1,這裡的**第6行就相當於pos and (not pos + 1),其結果是取出最右邊的那個1。這樣,p就表示該行的某個可以放子的位置,把它從pos中移除並遞迴呼叫test過程。注意遞迴呼叫時三個引數的變化,每個引數都加上了乙個禁位,但兩個對角線方向的禁位對下一行的影響需要平移一位。最後,如果遞迴到某個時候發現row=111111了,說明六個皇后全放進去了,此時程式從第1行跳到第11行,找到的解的個數加一。

~~~~====~~~~*****   華麗的分割線   *****~~~~====~~~~

gray碼

假如我有4個潛在的gf,我需要決定最終到底和誰在一起。乙個簡單的辦法就是,依次和每個mm交往一段時間,最後選擇給我帶來的「滿意度」最大的mm。但看了

dd牛的理論

後,事情開始變得複雜了:我可以選擇和多個mm在一起。這樣,需要考核的狀態變成了2^4=16種(當然包括0000這一狀態,因為我有可能是玻璃)。現在的問題就是,我應該用什麼順序來遍歷這16種狀態呢?

傳統的做法是,用二進位制數的順序來遍歷所有可能的組合。也就是說,我需要以0000->0001->0010->0011->0100->...->1111這樣的順序對每種狀態進行測試。這個順序很不科學,很多時候狀態的轉移都很耗時。比如從0111到1000時我需要暫時甩掉當前所有的3個mm,然後去把第4個mm。同時改變所有mm與我的關係是一件何等巨大的工程啊。因此,我希望知道,是否有一種方法可以使得,從沒有mm這一狀態出發,每次只改變我和乙個mm的關係(追或者甩),15次操作後恰好遍歷完所有可能的組合(最終狀態不一定是1111)。大家自己先試一試看行不行。

解決這個問題的方法很巧妙。我們來說明,假如我們已經知道了n=2時的合法遍歷順序,我們如何得到n=3的遍歷順序。顯然,n=2的遍歷順序如下:

0001

1110

你可能已經想到了如何把上面的遍歷順序擴充套件到n=3的情況。n=3時一共有8種狀態,其中前面4個把n=2的遍歷順序照搬下來,然後把它們對稱翻折下去並在最前面加上1作為後面4個狀態:

000001

011010 ↑

--------

110 ↓

111101

100用這種方法得到的遍歷順序顯然符合要求。首先,上面8個狀態恰好是n=3時的所有8種組合,因為它們是在n=2的全部四種組合的基礎上考慮選不選第3個元素所得到的。然後我們看到,後面一半的狀態應該和前面一半一樣滿足「相鄰狀態間僅一位不同」的限制,而「鏡面」處則是最前面那一位數不同。再次翻摺三階遍歷順序,我們就得到了剛才的問題的答案:

0000

0001

0011

0010

0110

0111

0101

0100

1100

1101

1111

1110

1010

1011

1001

1000

這種遍歷順序作為一種編碼方式存在,叫做gray碼(寫個中文讓蜘蛛來抓:格雷碼)。它的應用範圍很廣。比如,n階的gray碼相當於在n維立方體上的hamilton迴路,因為沿著立方體上的邊走一步,n維座標中只會有乙個值改變。再比如,gray碼和hanoi塔問題等價。gray碼改變的是第幾個數,hanoi塔就該移動哪個盤子。比如,3階的gray碼每次改變的元素所在位置依次為1-2-1-3-1-2-1,這正好是3階hanoi塔每次移動盤子編號。如果我們可以快速求出gray碼的第n個數是多少,我們就可以輸出任意步數後hanoi塔的移動步驟。現在我告訴你,gray碼的第n個數(從0算起)是n xor (n shr 1),你能想出來這是為什麼嗎?先自己想想吧。

下面我們把二進位制數和gray碼都寫在下面,可以看到左邊的數異或自身右移的結果就等於右邊的數。

二進位制數   gray碼

000       000

001       001

010       011

011       010

100       110

101       111

110       101

111       100

從二進位制數的角度看,「映象」位置上的數即是對原數進行not運算後的結果。比如,第3個數010和倒數第3個數101的每一位都正好相反。假設這兩個數分別為x和y,那麼x xor (x shr 1)和y xor (y shr 1)的結果只有一點不同:後者的首位是1,前者的首位是0。而這正好是gray碼的生成方法。這就說明了,gray碼的第n個數確實是n xor (n shr 1)。

今年四月份

mashuo

給我看了

這道題,是二維意義上的gray碼。題目大意是說,把0到2^(n+m)-1的數寫成2^n * 2^m的矩陣,使得位置相鄰兩數的二進位制表示只有一位之差。答案其實很簡單,所有數都是由m位的gray碼和n位gray碼拼接而成,需要用左移操作和or運算完成。完整的**如下:

varx,y,m,n,u:longint;

begin

readln(m,n);

for x:=0 to 1 shl m-1 do begin

u:=(x xor (x shr 1)) shl n; //輸出數的左邊是乙個m位的gray碼

for y:=0 to 1 shl n-1 do

write(u or (y xor (y shr 1)),' '); //並上乙個n位gray碼

writeln;

end;

end.

位運算簡介及實用技巧(三) 高階篇 2

今天我們來看兩個稍微複雜一點的例子。n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交us...

位運算簡介及實用技巧(三) 高階篇 2

今天我們來看兩個稍微複雜一點的例子。n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交us...

位運算簡介及實用技巧(三) 高階篇 2

n皇后問題位運算版 n皇后問題是啥我就不說了吧,學程式設計的肯定都見過。下面的十多行 是n皇后問題的乙個高效位運算程式,看到過的人都誇它牛。初始時,upperlim 1 shl n 1。主程式呼叫test 0,0,0 後sum的值就是n皇后總的解數。拿這個去交usaco,0.3s,暴爽。proced...