1. 程式人生 > >[Sicily 1150 1151 1515 魔板] BFS+判重(康託展開)

[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.