先介紹八數碼問題:
我們首先從經典的八數碼問題入手,即對於八數碼問題的任意一個排列是否有解?有解的條件是什麼?
我在網上搜了半天,找到一個十分簡潔的結論。八數碼問題原始狀態如下:
1 2 3
4 5 6
7 8
為了方便討論,我們把它寫成一維的形式,並以0代替空格位置。那麼表示如下:
1 2 3 4 5 6 7 8 0
通過實驗得知,以下狀態是無解的(交換了前兩個數字1 2):
2 1 3 4 5 6 7 8 0
八數碼問題的有解無解的結論:
一個狀態表示成一維的形式,求出除0之外所有數字的逆序數之和,也就是每個數字前面比它大的數字的個數的和,稱為這個狀態的逆序。
若兩個狀態的逆序奇偶性相同,則可相互到達,否則不可相互到達。
由於原始狀態的逆序為0(偶數),則逆序為偶數的狀態有解。
也就是說,逆序的奇偶將所有的狀態分為了兩個等價類,同一個等價類中的狀態都可相互到達。
簡要說明一下:當左右移動空格時,逆序不變。當上下移動空格時,相當於將一個數字向前(或向後)移動兩格,跳過的這兩個數字要麼都比它大(小),逆序可能±2;要麼一個較大一個較小,逆序不變。所以可得結論:只要是相互可達的兩個狀態,它們的逆序奇偶性相同。我想半天只能說明這個結論的必要性,詳細的證明請參考後面的附件。
>推廣二維N×N的棋盤
我們先來看看4×4的情況,同樣的思路,我們考慮移動空格時逆序的變化情況。
1 2 3 4
5 6 7 8
9 A B C
D E F
我們用字母A~F代替數字10~15。同樣地,當左右移動的時候,狀態的逆序不改變。而當上下移動的時候,相當於一個數字跨過了另外三個格子,它的逆序可能±3或±1,逆序的奇偶性必然改變。那麼又該如何
1 2 3 4
5 6 7 8
9 A B
C D E F
可以證明,以上狀態是一個無解的狀態(將C移到了第四行)。該狀態的逆序為0,和原始狀態相同,但是它的空格位置在第三行。若將空格移到第四行,必然使得它的逆序±1或±3,奇偶性必然改變。所以它是一個無解的狀態。
然而以下狀態就是一個有解的狀態(交換了前兩個數字1 2):
2 1 3 4
5 6 7 8
9 A B
C D E F
這個狀態的逆序為1,和原始狀態奇偶性不同,而空格位置在第三行。由於空格每從第三行移動到第四行,奇偶性改變。則該狀態的可到達原始狀態。
通過觀察,得出以下結論:
N×N的棋盤,N為奇數時,與八數碼問題相同。
N為偶數時,空格每上下移動一次,奇偶性改變。稱空格位置所在的行到目標空格所在的行步數為空格的距離(不計左右距離),若兩個狀態的可相互到達,則有,兩個狀態的逆序奇偶性相同且空格距離為偶數,或者,逆序奇偶性不同且空格距離為奇數數。否則不能。
也就是說,當此表示式成立時,兩個狀態可相互到達:(狀態1奇偶性==狀態2奇偶性)==(空格距離%2==0)。
此結論只是由觀察得知的,但是還沒證明過,請高手指點。
另外再詳細說明一下,無論N是奇數還是偶數,空格上下移動,相當於跨過N-1個格子。那麼逆序的改變可能為一下值±N-1,±N-3,±N-5 …… ±N-2k-1。當N為奇數數時N-1為偶數,逆序改變可能為0;當N為偶數時N-1為奇數,逆序的改變不能為0,只能是奇數,所以沒上下移動一次奇偶性必然改變。
>推廣到三維N×N×N
其實,三維的結論和二維的結論是一樣的。
考慮左右移動空格,逆序不變;同一層上下移動空格,跨過N-1個格子;上下層移動空格,跨過N^2-1個格子。
當N為奇數時,N-1和N^2-1均為偶數,也就是任意移動空格逆序奇偶性不變。那麼逆序奇偶性相同的兩個狀態可相互到達。
當N為偶數時,N-1和N^2-1均為奇數,也就是令空格位置到目標狀態空格位置的y z方向的距離之和,稱為空格距離。若空格距離為偶數,兩個逆序奇偶性相同的狀態可相互到達;若空格距離為奇數,兩個逆序奇偶性不同的狀態可相互到達。
討論到這裡,題目問題也就解決了。
另外水木清華BBS論壇中有人提到:
8數碼難題搜尋時,有時候是無解的,8數碼問題總共有9!種狀態,如果用計算機一
個一個去搜尋去判斷哪些有解哪些誤解,無疑要花費很長的時間,也沒必要。作為一種
智力遊戲,玩之餘,我在想,能否通過事先的分析來判斷哪些問題有解,哪些問題無解
呢?對於有解的問題,是否能通過一種固定的步驟來得到解?經過昨天晚上和今天的仔
細分析,我覺得我已經得到了這個問題的初步解答,下面我公佈一下我得到的結果和證
明,拋磚引玉,如果大家發現其中有什麼問題,歡迎來我宿舍一起討論或者給我發Emai
l。
我的證明分好幾個步驟,懇請大家能夠耐心的看下去,我會盡量說得簡潔一點。
一、我的結論
我們將九宮格按行排成一行共九個數(空格也佔一個位置,在本文種,我用@表示空
格)。比如: 1 2 3 4 @ 5 => 1 2 3 4 @ 5 6 7 8 6 7 8 這樣
,九宮格的每一種狀態和上圖的行之間是一一對應的。在代數上冊我們學過逆序的概念
,也就是對於任意一對數,如果前面的數比後面的數大,則為一對逆序。對於一個序列
,我們定義其逆序奇偶性如下: 如果其中有奇數對逆序,稱之逆序奇;如果其中有
偶數對逆序,稱之為逆序偶。這樣,對於1,2,...,n這n個數,其所有的排列可以分為
兩類,逆序奇類和逆序偶類。相對應的,九宮圖(除去空格)也有它的奇偶性,我們的定
理是:
所有的奇九宮圖之間是可達的,所有的偶九宮圖之間也是可達的,但奇九宮圖和偶
九宮圖之間互不可達。
二、問題的轉化
為了證明上述定理,我想先對問題進行一下轉化。我定義兩種行序列的變換:一種
是空格@和相鄰的數對換,一種是空格@和前後隔兩個數的數之間的對換,前者對應著空
格在九宮圖中的左右移動,後者對應著空格在九宮圖中的上下移動。
引理一:在上述的兩種對換下,序列的奇偶性不改變。 這個引理很容易證明。
首先,相鄰的對換肯定不改變奇偶性;其次,隔兩格的對換也不改變奇偶性,它相當於
三個數的輪換,我們可以自己列舉一下幾種情況驗證一下。這就說明了奇九宮圖和偶九
宮圖之間是互不可達的。
引理二:轉化後行序列在上面定義的兩種對換下的任意操作,可以轉換成九宮圖中
空格的合法變化。 這個引理也是比較容易證明的。我們只要證明如下的幾種狀態之
間是互達的: a b c a b @ a b c a b c @ d e <=>
c d e d e f <=> d e @ f g h f g h @ g h f
g h 通過計算機搜尋,可以發現上面兩對狀態之間的確是互達的。從而,我們可以
假定上面兩種狀態之間的轉換可以用行序列中的兩種鄰對換來代替。
想提一點的是,九宮圖之間的變換是可逆的。下面,到了問題的核心部分了。
引理三:所有的奇狀態可以轉換為 @ 1 2 3 4 5 6 7 8, 所有的偶狀態
可以轉換為 @ 2 1 3 4 5 6 7 8. 要證明這個引理,得分幾個步驟。我的想法是先設
法把8移到最後一個,然後8保持不動(注意,我們這裡的不動只是形式上不動,但不管怎
樣,我們的每一個變換後,8還是保持在最後一個,餘類似),再將7移到8之前,然後,
保持7和8不動,依次移動6,5,4,3,得到 * * * 3 4 5 6 7 8這裡 * * *是 @ 1 2 的
一個排列。到這裡,我想要得到前面的兩種狀態之一是顯然的了。下面,我說明,上面
的想法是可以實現的。如下:
1. 對於 a b c @ ,我們可以將其中的任意一個移到
最後,並且對變換僅限於這四個位置上。顯然,對於a,c是一步就可以做到的。對於b,
步驟如下: a b c @ -> @ b c a -> b @ c a -> b c @ a -> b c a @ -> @ c a b
2. 先把要移到最後位置的那個數移到最後四個位置之一,然後再將空格移到最後一
個位置,用1的方法將待移動的數變換到最後一個位置。迴圈這樣做即可。 引理三證
畢。
證完了這三個引理,定理的成立就是顯然的了。首先,將奇偶性相同的兩種狀態都
變換到上述兩種標準狀態之一,然後對其一去逆變換即可。以上是我的所有證明,有些
地方在計算機上寫起來不是太清楚。
ps:http://acm.hdu.edu.cn/showproblem.php?pid=3600
#include <stdio.h> int a[90010],b[90010],nxs; void mergeSort(int first,int last) { int mid,i; int begin,m,end; if(first+1<last) { mid=(first+last)/2; //merge(first,mid,last); begin=first; m=mid; i=first; mergeSort(first,mid); mergeSort(mid,last); while(begin<mid && m<end) { if(a[begin]<=a[m]) { b[i++]=a[begin++]; } else { b[i++]=a[m++]; //for(i=beginA;i<=mid;i++) //printf("nixuyou:%d,%d\n",a[i],a[beginB]); nxs+=mid-begin; } } while(begin<mid) { b[i++]=a[begin++]; } while(m<last) { b[i++]=a[m++]; } /*for(i=0;i<j;i++) { a[first+i]=b[i]; }*/ while(first<last) { a[first]=b[first++]; } } } int main() { int n,sum,i,iwz,hangshu,hangshucha,panduanshu,j,x; while(scanf("%d",&n)==1 && n) { sum=n*n; j=0; for(i=0;i<sum;i++) { scanf("%d",&x); if(x==0) { iwz=i; } else { a[j]=x; j++; } } nxs=0; mergeSort(0,sum-1); //printf("nxs=%d\n",nxs); if(n%2==1) //n為奇數 { if(nxs%2==0) printf("YES\n"); else printf("NO\n"); } else //n為偶數 { hangshu=iwz/n; hangshucha=n-1-hangshu; //printf("hangshucha=%d\n",hangshucha); panduanshu=nxs+hangshucha; if(panduanshu%2==0) printf("YES\n"); else printf("NO\n"); } } return 0; }
//題解:八數碼問題,逆序數 //程式碼: #include<stdio.h> #define SIZE_N 90010 int ary[2][SIZE_N]; int sum,len; void reverseNum(int l,int h,int idx) { int i,j,k; int mid,idxt; idxt = 1 - idx; if(l == h) { ary[1][l] = ary[0][l]; return ; } mid = (l + h) / 2; reverseNum(l, mid, idxt); reverseNum(mid+1, h, idxt); for(i = j = l,k = mid+1;i <= mid && k <= h;) { if(ary[idx][i] < ary[idx][k]) { ary[idxt][j++] = ary[idx][i]; sum += k - mid - 1; i ++; } else { ary[idxt][j++] = ary[idx][k]; k ++; } } for(i;i <= mid;i ++) { ary[idxt][j++] = ary[idx][i]; sum += h - mid; } for(k;k <= h;k ++) { ary[idxt][j++] = ary[idx][k]; } } int main() { int n; int i; while(scanf("%d",&n),n != 0) { len = n * n; for(i = 0;i < len;i ++) { scanf("%d",&ary[0][i]); if(ary[0][i] == 0) { if(!(n & 1)) { sum = (n - i / n - 1); } else { sum = 0; } i --; len --; } } if(n == 1) { puts("YES"); continue; } reverseNum(0, len-1, 0); if(sum & 1) { puts("NO"); } else { puts("YES"); } } return 0; }