[Sicily 1150 1151 1515 魔板] BFS+判重(康託展開)
1150、1151、1515都是魔板的題目,只是資料規模以及一些操作不同,1515的步數上限N最大會達到100。所以,我選擇直接解決1515魔板C。這樣就相當於同時解決了1150、1151、1515
(1)魔板狀態以及對應操作序列的儲存
定義一個struct結構體,該結構體有兩個成員,第一個是2行4列的陣列m,存放魔板的狀態,第二個是string型別的operation,存放達到這個狀態對應的操作序列。
struct Status
{
int m[2][4];
string operation;
};
(2)實現三種基本操作
實現函式Status change_m(Status a, int op)
該函式兩個引數,一個是Status型別的a,一個是int型別的op,op=1、2、3,分別表示執行A、B、C操作,該函式的功能是對a執行一個op操作,然後返回執行完該操作的Status型別。
要注意的是,要對應改變魔板狀態,也就是對a.m[ ][ ]賦值,並且a.operation = operation+“A”(或“B”或“C”),然後返回a就可以了。
(3)實現返回值為布林型別的函式來判斷魔板狀態是否等於目標狀態
bool match(int ma[2][4], int mb[2][4])
該函式的一個引數是當前魔板狀態,另一個引數是目標魔板狀態。只要分別判斷數組裡對應位置的數是否都相等就可以了。
(4)BFS
要對三叉樹進行BFS,第一個可行解即是最優解。
BFS基本思想:
這時候,BFS的佇列裡存的型別是我之前定義的Status,一開始把初始狀態push進去,然後只要佇列不為空,就取出隊首元素,對隊首元素進行A、B、C三種操作的擴充套件,把擴充套件完的三種Status加入到佇列中去,然後pop隊首元素。一直迴圈直到佇列為空。
對於這道題的BFS,還要增加以下兩個判斷:
一是每次取隊首元素的時候要判斷隊首元素的操作序列長度,如果大於步數上限,就打印出-1,返回。
二是每次還要判斷隊首元素的魔板狀態是否等於目標狀態,如果等於則打印出對應操作序列長度,以及操作序列,然後返回。
(5)判重
不同的操作序列可能會產生相同的魔板狀態,例如AB和BA對應的魔板狀態是一樣的,當這種情況出現時,我們就不應該再對這兩種相同的魔板狀態進行擴充套件。
所以,BFS每經過一個節點,就要把對應的魔板狀態放入已搜尋列表中,如果當前節點在已搜尋列表中存在,則不再擴充套件該節點。在這裡,擴充套件的意思是在當前操作上加一步操作(A、B或C),一個節點擴充套件為三個。
這個魔板有8個元素,所以一共有8!=40320個節點。
(6)康託展開
康託展開是一個全排列到一個自然數的一一對映,常用於構建雜湊表時的空間壓縮。
我們首先定義一個數組
初始化visited裡每個元素為0。
利用康託展開,我們就可以直接得到當前魔板狀態對應的自然數,然後把visited[當前對應的自然數]=1,表示已經搜尋過該狀態。
(7)康託展開的基本思想
康託展開其實就是對全排列進行編號。求對於當前這個排列,有多少個排列的數值比它小,對應的自然數就是多少。
例如對於1324,第一位是1,小於1的數為0,對應0 * 3!= 0
第二位是3,小於3的數有1和2,但是1已經在第一位出現了,所以1 * 2!= 2
第三位是2,小於2的數有1,但是 1已經在前面出現過,所以0 * 1!= 0
0 + 2 + 0 = 2
所以小於1324的有兩個,康託展開後,1324對應的自然數為2
理解了思想就可以寫程式碼實現康託展開了。
(8)階乘
康託展開裡涉及到階乘,所以要實現求階乘的函式。
程式碼實現:
#include<iostream>
#include<string>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int target_m[2][4];//目標魔板狀態
int start_m[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};//開始魔板狀態
int visited[41000];//8!=40320,所以節點的狀態一共有40320種
struct Status
{
int m[2][4];
string operation;
};
Status initial;
int factorial(int number)//求階乘
{
int fac = 1;
while(number > 0)
{
fac = fac * number;
number--;
}
return fac;
}
int Cantor(Status& current)//康託展開
{
int cantor = 0;
int vis[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 4; j++)
{
vis[current.m[i][j]] = 1;
int less = current.m[i][j] - 1;//小於當前這個數的數有less個
int l = less;
for(int k = 1; k <= l; k++)
{
if(vis[k])//雖然小於當前這個數,但是已經在前面出現過了
less = less - 1;
}
cantor = cantor + less * factorial(8 - 4 * i - j - 1);
}
}
return cantor;
}
Status change_m(Status a, int op)//在這裡Status a不可以按引用傳遞,因為bfs要對同一個狀態執行A、B、C操作,所以不可以在執行A操作的時候就把它改了。
{//返回的是增加一個操作的status
if(op == 1)//左右兩列互換
{
int tmp1 = a.m[0][0];
a.m[0][0] = a.m[0][2];
int tmp2 = a.m[0][1];
a.m[0][1] = a.m[0][3];
int tmp3 = a.m[1][0];
a.m[1][0] = a.m[1][2];
int tmp4 = a.m[1][1];
a.m[1][1] = a.m[1][3];
a.m[0][2] = tmp1;
a.m[0][3] = tmp2;
a.m[1][2] = tmp3;
a.m[1][3] = tmp4;
a.operation = a.operation + "A";
}
else if(op == 2)//每次以行迴圈左移一個
{
int tmp1 = a.m[0][0];
int tmp2 = a.m[1][0];
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 3; j++)
{
a.m[i][j] = a.m[i][j + 1];
}
}
a.m[0][3] = tmp1;
a.m[1][3] = tmp2;
a.operation = a.operation + "B";
}
else//中間四小塊逆時針轉一格
{
int tmp1 = a.m[0][1];
a.m[0][1] = a.m[0][2];
a.m[0][2] = a.m[1][2];
a.m[1][2] = a.m[1][1];
a.m[1][1] = tmp1;
a.operation = a.operation + "C";
}
return a;
}
bool match(int ma[2][4], int mb[2][4])
{
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 4; j++)
{
if(ma[i][j] != mb[i][j])
return false;
}
}
return true;
}
void bfs(int n)
{
queue<Status> q;
q.push(initial);
while(!q.empty())
{
//取隊首元素
Status top = q.front();
if(top.operation.length() > n)
{
cout << "-1" << endl;
return;
}
if(match(top.m, target_m))
{
cout << top.operation.length() << " " << top.operation << endl;
return;
}
if(!visited[Cantor(top)])//如果當前節點沒有被visited過,則擴充套件該節點
{
for(int i = 1; i <= 3; i++)
{
Status cur = change_m(top, i);
q.push(cur);
}
visited[Cantor(top)] = 1;
}
q.pop();
}
}
int main()
{
int n;
//初始化魔板
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 4; j++)
initial.m[i][j] = start_m[i][j];
}
initial.operation = "";
while(cin >> n && n != -1)//n表示最多容許步數
{
fill(visited, visited + 41000, 0);//初始化visited
for(int i = 0; i < 2; i++)//輸入目標魔板
for(int j = 0; j < 4; j++)
cin >> target_m[i][j];
bfs(n);
}
return 0;
}
Finally, good luck to you.