M*N PUZZLE類似與八數位問題, 其實這個問題滿熱門的,而且有著很多的擴充。
題目中說道N 與 M中有一個為奇數, 那麼自然就想到了用逆序對數可以解決這個問題,其簡單的證明在以前寫過個八數位報告裡就有:(註:將空格去除,只考慮有數位格子)
1 對於左右移動是不影響整個序列的逆序的。
2 對於奇數列的PUZZLE,做上下移動無異與左右移動偶數個位置,至少不會改變逆序的奇偶性。
這是比較隨便的證明,其實更深層次可以到組合數學裡找找相關知識(去年四川大的網路預選賽上有類似題,當時是領隊找出了組合上的解決方案我們才過的,不過我已經忘了差不多了)。
這樣,題目可以分解為: 將輸入轉化為 奇數列的二維數組,同時構建一個目標態的數組 -> 都轉化一維數組(序列)且去除0 -> 求兩個序列的逆序對 -> 判斷同奇同偶輸出結果。
最佳化一: 直接將輸入儲存在一維數組中
PKU上的解題報告提到了保留空格,那麼可以直接將輸入處理為一維數組,代價是計算空格的曼哈頓距離。
如果輸入的列就是奇數,那麼問題就簡單了,如果輸入列是偶數,那麼需要做個下標轉換:
輸入列n為奇數: 對於i行j列來說,他在一維序列上的位置是i * n + j ;
輸入列n為偶數,則轉換為列數m:對於行j列來說,他在一維序列上的位置是(j+1) * n - 1 - i。
最佳化二:無需計算曼哈頓距離
為了不計算曼哈頓距離,那麼就必須事先將0去掉。從表面上看這個工作比求曼哈頓距離要煩,其實只要手法巧妙,代碼量不會很多。而且去除0的好處主要是為最佳化三做考慮。
輸入列n為奇數: 因為輸入是順序的,只要在遇到0時將標記flag設定一下,以後的值的下標位置改為原idx-1。
輸入列轉換為列數m:比較麻煩,需記錄0的位置,然後做一個while()把0以後的數前移動,如果使用鏈表的話操作就簡單了,直接刪除0就可以了(但我想一般是用數組做的吧)。
最佳化三:直接計算目標態的逆序
這樣就可以為每個CASE省略一次求逆序對數的操作。
對於列n為奇數:就是目標態是最簡單的1到n*m-1,再加個0,那麼逆序數是0,因為就是順序的,0已忽略。
輸入列轉換為列數m:寫兩個資料就可以得到規律了,將一個順序的態轉換一下得到轉換後的目標態的每個位置上的數的逆序數有下面規律
第1到m個的逆序分別為 0 1 2 3 4 ... m-1
第m+1到2*m 為 0 2 4 6 8 ... 2*(m-1)
第三行 0 3 6 9 ... 3*(m-1)
.....
那麼有規律,第i行的逆序和為i*(n*(n-1)/2),對所有行求和得 n*(n-1)*m*(m+1)/4,但小心最後一行因為有個位置換成0而不是n*m,所以最後一行少掉m-1個逆序,所以要減去m-1。
一和二是比較簡單的最佳化,甚至二可以不用,對三做點改動也行。三是主要通過直接計算就避免了一半的求逆序工作。