HDU 3600 Simple Puzzle 歸併排序 N*N數碼問題

尋找&星空の孩子發表於2013-09-23

先介紹八數碼問題:

 

我們首先從經典的八數碼問題入手,即對於八數碼問題的任意一個排列是否有解?有解的條件是什麼?

 

我在網上搜了半天,找到一個十分簡潔的結論。八數碼問題原始狀態如下:

 

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;
}

 

相關文章