LuoguP4289 [HAOI2008]移動玩具 題解
阿新 • • 發佈:2021-12-23
LuoguP4289 [HAOI2008]移動玩具 題解
主要參照這篇題解(第一頁第一篇),作者為本人教練。
講解在註釋裡都有,自己看看吧。
#include <cstdio> #include <cmath> #include <algorithm> #include <queue> using namespace std; int pre[6][6], tar[6][6], vis[65539], f[65539], dec1, dec2; queue<int> q; const int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1}; /* 變數: pre陣列表示初始狀態,dec1表示初始狀態轉化的十進位制數。 tar陣列表示目標狀態,dec2表示目標狀態轉換的十進位制數。 vis陣列表示是否被訪問過。 q佇列用來儲存玩具擺放的狀態。 dx和dy陣列是為了方便表示擴充套件而使用的。 有關玩具放置的狀態的轉化請移步至函式1。 */ int BintoDec(int a[6][6]) { /* 函式1:int BintoDec(int a[6][6]) 此函式的目的: 根據a陣列,將玩具擺放的狀態轉化為十進位制數。 變數解釋: a陣列表示要轉換的玩具擺放的狀態。 具體思想: 由於每種玩具擺放的狀態都是唯一的, 所以我們嘗試著將其轉化為一個數字。 大家可以發現,每種玩具擺放的狀態其實是二進位制的表示。 所以我們可以將其轉化為十進位制的數。 例如:樣例中,初始狀態可以轉化為:1111000011100010。 所以可以將其轉化為十進位制的數。 可能你會考慮爆long long或爆空間的問題。 放心,玩具擺放的狀態用十進位制表示必不會超過2^16-1。 所以,我們可以將樣例中的起始狀態和目標狀態 分別表示為61666和42405。 具體怎麼轉化呢? 我們先畫一個玩具擺放的狀態圖: 1 2 3 4 1 15 14 13 12 2 11 10 9 8 3 7 6 5 4 4 3 2 1 0 可以發現,每個格子對應的乘方有如下的規律: 設格子(i,j)對應的乘方為G(i,j), 則有:G(i,j) = 16 - (i - 1) * 4 - j。 這個稍微想一下就可以證明出來,這裡不再贅述。 */ int res = 0; for(int i = 4; i >= 1; --i) for(int j = 4; j >= 1; --j) res += a[i][j] * pow(2, 16 - (i - 1) * 4 - j); return res; } void DectoBin(int x, int a[6][6]) { /* 函式2:void DectoBin(int x, int a[6][6]) 此函式的目的: 根據x,將十進位制狀態轉換回一開始二進位制的狀態。 變數解釋: x表示要轉換回的十進位制數, a陣列表示轉換後的二進位制狀態。 具體思想: 呃。。。這就不用解釋了吧。 這個大家應該很明白吧。 不明白就去看有關二進位制的內容吧。 */ while(x) { for(int i = 4; i >= 1; --i) for(int j = 4; j >= 1; --j) { a[i][j] = x % 2; x /= 2; } } //轉換完畢 } int judge(int x0, int y0, int xx, int yy) { /* 函式3:int judge(int x0, int y0, int xx, int yy) 此函式的目的: 判斷是否越過邊界且點所表示的數是否相同,是返回1,否表示0 變數解釋: xx,yy分別是點(x0,y0)經過擴充套件後得到的點的橫座標和縱座標。 具體思想:\ */ return (xx >= 1) && (xx <= 4) && (yy >= 1) && (yy <= 4) && (pre[x0][y0] != pre[xx][yy]); } void bfs() { /* 函式4:void bfs() 此函式的目的: 通過bfs求得最少步數。 變數解釋:\ 具體思想: 基本的bfs(STL<queue>)操作: 1. 將初始狀態入隊,並標記在佇列中(vis[x]=1) 2. 在佇列不為空時迴圈執行以下操作: (1)取出隊首,將其轉化為原來的二進位制狀態,然後出隊。 (2)向上下左右擴充套件。 (3)判斷是否越過邊界且兩數相等。 if No then執行以下操作(最後需要還原a陣列狀態!): 1_ 提取擴充套件節點的狀態,並將其轉化為十進位制數。 2_ 將擴充套件節點與原數交換 3_ 記錄交換後的陣列對應的十進位制數 4_ 判斷是否訪問過 5_ 若沒訪問過,則標記已訪問過,並記錄交換後的陣列對應的十進位制數的父節點,即交換前的陣列對應的十進位制數。 6_ 否則直接跳過。 7_ 如果已經達到了目標狀態,直接返回。 else then直接跳過 */ q.push(dec1); vis[dec1] = 1;/*注意!一定要加!!!*/ while(q.size()/*或者可以寫!q.empty()*/) { int now = q.front(); DectoBin(now, pre); q.pop(); for(int i = 1; i <= 4; ++i) for(int j = 1; j <= 4; ++j) { int x0 = i, y0 = j; for(int k = 0; k < 4; ++k) { int xx = x0 + dx[k], yy = y0 + dy[k], flag = 0; if(judge(x0, y0, xx, yy)) { flag = 1; int tmpdec1 = BintoDec(pre); swap(pre[x0][y0], pre[xx][yy]); int tmpdec2 = BintoDec(pre); if(!vis[tmpdec2]) { vis[tmpdec2] = 1; f[tmpdec2] = tmpdec1; q.push(tmpdec2); } if(tmpdec2 == dec2) return; } if(flag) swap(pre[x0][y0], pre[xx][yy]); } } } } int main() { /* Step 1:輸入&轉化為二進位制 由於輸入形式中,數字都是連在一起的。 所以,大多數人都會想到用字串或字元陣列輸入每一行, 然後將其轉化為數字。但其實可以不用這麼麻煩。 大家都知道,佔x位輸入是這麼寫的: scanf("%xd", &a);(假定x是具體數字) 所以,對於一行連在一起的數字,我們可以佔一位輸入。 就是上面所寫的那樣。這樣就可以使程式碼簡潔明瞭。 然後再呼叫上面的BintoDec函式得到初始和目標狀態的十進位制數。 */ for(int i = 1; i <= 4; ++i) for(int j = 1; j <= 4; ++j) scanf("%1d", &pre[i][j]); for(int i = 1; i <= 4; ++i) for(int j = 1; j <= 4; ++j) scanf("%1d", &tar[i][j]); dec1 = BintoDec(pre), dec2 = BintoDec(tar); /* Step 2:bfs一波走著! 根據上面的函式,保證會有解。 */ bfs(); f[dec1] = 0; int ans = 0; /* Step 3:遞進求答案、輸出&程式結束 */ while(f[dec2]) ans++, dec2 = f[dec2]; printf("%d", ans); return 0; }