Gray碼(轉自M67大牛)
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的遍曆順序如下:
00
01
11
10
你可能已經想到了如何把上面的遍曆順序擴充到n=3的情況。n=3時一共有8種狀態,其中前面4個把n=2的遍曆順序照搬下來,然後把它們對稱翻折下去並在最前面加上1作為後面4個狀態:
000
001
011
010 ↑
--------
110 ↓
111
101
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運算完成。完整的代碼如下:
var
x,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.
PS:
一般的,普通二進位碼與格雷碼可以按以下方法互相轉換:
二進位碼->格雷碼(編碼):從最右邊一位起,依次將每一位與左邊一位異或(XOR),作為對應格雷碼該位的值,最左邊一位不變(相當於左邊是0);
格雷碼->二進位碼(解碼):從左邊第二位起,將每位與左邊一位解碼後的值異或,作為該位解碼後的值(最左邊一位依然不變)。
ZOJ 2531是典型的Gray碼http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1533
int toGray(int x){//轉化為格雷碼 return x^(x>>1);}int toBinary(int x){//格雷碼轉化為二進位 int y = x; while(x>>=1){ y ^= x; } return y;}int main(){ int n,m; while(scanf("%d%d",&n,&m)&&(n+m)){ int i,j; int beg = toBinary(m); for(i=beg;i<n;i++){ printf("%d ",toGray(i)); } for(i=0;i<beg;i++){ printf("%d ",toGray(i)); } puts(""); } return 0;}
POJ 1832http://poj.org/problem?id=1832 需要用到大整數
import java.io.*;import java.math.*;import java.util.*;public class Main{static BigInteger[] base = new BigInteger[130];static void init(){int i,j;base[0] = BigInteger.ONE;for(i=1;i<128;i++){base[i] = base[i-1].multiply(BigInteger.valueOf(2));}}public static void main(String args[]){init();int t;Scanner cin = new Scanner(System.in);t = cin.nextInt();int[] a = new int[130];int[] b = new int[130];while((t--)!=0){int i,j;int n = cin.nextInt();a[0] = cin.nextInt();for(i=1;i<n;i++){a[i] = cin.nextInt();a[i] = a[i]^a[i-1];}b[0] = cin.nextInt();for(i=1;i<n;i++){b[i] = cin.nextInt();b[i] = b[i]^b[i-1];}BigInteger aa = BigInteger.valueOf(0);BigInteger bb = BigInteger.valueOf(0);for(i=0;i<n;i++){aa = aa.add(base[i].multiply(BigInteger.valueOf(a[n-i-1])));bb = bb.add(base[i].multiply(BigInteger.valueOf(b[n-i-1])));}BigInteger ans = bb.subtract(aa).abs();System.out.println(ans);}}}