Sicily 1151. 魔板

来源:互联网 发布:淘宝潮男衣服店铺推荐 编辑:程序博客网 时间:2024/04/20 08:57

题目链接在此

经典的状态转换搜索题。



一.原题中文大意
(1)描述
魔板由8个大小相同方块组成,分别用涂上不同颜色,用1到8的数字表示。其初始状态是:
1 2 3 4
8 7 6 5
对魔板可进行三种基本操作:
① A操作(上下行互换):
8 7 6 5
1 2 3 4

② B操作(每次以行循环右移一个):
4 1 2 3
5 8 7 6

③ C操作(中间四小块顺时针转一格):
1 7 2 4
8 6 3 5
用上述三种基本操作,可将任一种状态装换成另一种状态。


(2)输入
输入包括多个要求解的魔板,每个魔板用三行描述。
第一行步数N(可能超过10的整数),表示最多容许的步数。
第二、第三行表示目标状态,按照魔板的形状,颜色用1到8的表示。
当N等于-1的时候,表示输入结束。


(3)输出
对于每一个要求解的魔板,输出一行。
首先是一个整数M,表示你找到解答所需要的步数。接着若干个空格之后,从第一步开始按顺序给出M步操作(每一步是A、B或C),相邻两个操作之间没有任何空格。
注意:如果不能达到,则M输出-1即可。


(4)输入样例
4 5 8 7 6 4 1 2 3
3 8 7 6 5 1 2 3 4
-1


(5)输出样例

2 AB 

1 A


二.数据结构与算法思想
(1) 数据结构 
①用字符串记录每个魔板的状态,如初始状态记作"12348765";


②用结构体记录每个魔板的状态和它从起始状态到该状态的路径:

struct boardTree {string board;string path;};


③广度优先搜索要用到队列,本解法使用STL中的queue:

queue<boardTree>


④用数组存储是否访问过某状态:
由于最多有8!=40320个状态,设置数组 bool visit[40320]来记录。


(2)算法思想
①使用广度优先搜索算法搜索状态;

②用康拓展开压缩状态,将8!=40320个状态所对应的40320种全排列顺序一一映射成常数,作为visit数组的下标。

③剪枝:由于任何一状态经过AA,或BBBB,或CCCC操作后会还原该状态,所以对于路径中出现“AA”,或“BBBB”,或“CCCC”子串的状态,不予进行判断或将其子状态放入队列。



三.详细解题思路 
(1)广度优先搜索算法 

①先将初始状态魔板放入队列。

②当队列不为空时,

a.检查队首魔板的路径长度是否超出限制,若超出,跳过b,进入c;
b.检查队首魔板的状态是否与终态一致,若一致则输出结果。否则进入c。
c.将该状态分别经过A、B、C操作所的状态依次放入队列。队首出队,重复步骤②。


(2)康托展开 
康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。康托展开的实质是计算当前排列在所有由小到大全排列中的顺序。其计算公式如下:
把一个整数X展开成如下形式:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0![1] 
其中,a为整数,并且0<=a[i]<i(1<=i<=n)


(3)A、B、C操作的实现
主要思想是将“旧”魔板的状态中每一位的值按规律映射到“新”魔板的状态中每一位的值:
string operationA(string board) {string tmp = "";int index[8] = { 4, 5, 6, 7, 0, 1, 2, 3 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}

string operationB(string board) {string tmp = "";int index[8] = { 3, 0, 1, 2, 7, 4, 5, 6 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}

string operationC(string board) {string tmp = "";int index[8] = { 0, 5, 1, 3, 4, 6, 2, 7 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}


四.逐步求精算法描述
本题解最重要的部分为搜索算法:
①主要是利用construct这一函数实现:传入的是目标状态(string target),路径深度限制(int limit),标记是否成功找到目标的布尔值(bool& flag),魔板状态队列(queue<boardTree>& treeQ),以及用以判重的访问数组(bool visit[])。


②当队列为空时结束循环并判定查找失败,否则进行循环:

a.记录队首元素(root)并使其出队。
b.利用康拓展开获得root的状态对应在全排列中的位置,并将其当做下标查看visit数组,判断root的状态是否访问过,若是则跳过当次循环。
c.判定当前路径长度是否超过限制,若是则跳过当次循环。
d.剪枝:判定当前路径的末尾是否出现“AA”或“BBBB”或“CCCC”的子串,若是则跳过当次循环。
e.若root的状态恰好是目标状态,则输出结果并跳出循环使得函数返回。
f.若非目标状态则依此将root经过A、B、C操作所得的状态推入队列。
  
 

五.程序注释清单

#include<iostream>#include<string>#include<string.h>#include<queue>using namespace std;#define PERMUTATION_SIZE 8  //  参与全排列元素的个数#define PERMU 40320  //  全排列总数 bool visit[PERMU];  //  访问数组const int factory[] = { 0, 1, 2, 6, 24, 120, 720, 5040 };  //  前8个阶乘数string startPattern = "12348765";  //  初始状态字符串//  康拓展开函数int cantor(string buf) {int counted;int result = 0;//  找出改状态对应的全排列在所有全排列中的次序(从0开始计数)for (int i = 0; i < PERMUTATION_SIZE; i++) {counted = 0;for (int j = i + 1; j < PERMUTATION_SIZE; ++j)if (buf[i] > buf[j])counted++;result += counted * factory[PERMUTATION_SIZE - i - 1];}return result;}  //  魔板状态数据结构:结构体struct boardTree {string board;  //  状态对应的字符串string path;  //  路径对应的字符串int depth;  //  当前深度boardTree() {}boardTree(string b, string p, int d) : board(b),path(p), depth(d) {}};  //  操作Astring operationA(string board) {string tmp = "";int index[8] = { 4, 5, 6, 7, 0, 1, 2, 3 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}  //  操作Bstring operationB(string board) {string tmp = "";int index[8] = { 3, 0, 1, 2, 7, 4, 5, 6 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}  //  操作Cstring operationC(string board) {string tmp = "";int index[8] = { 0, 5, 1, 3, 4, 6, 2, 7 };for (int i = 0; i < 8; i++)tmp += board[index[i]];return tmp;}void construct(string target, int limit, bool& flag, queue<boardTree>& treeQ, bool visit[]) {while (!treeQ.empty()) {  //  记录队首元素(root)并使其出队boardTree root = treeQ.front();treeQ.pop();  //  利用康拓展开获得root的状态对应在全排列中的位置,并将其当做下标查看visit数组,判断root的状态是否访问过,若是则跳过当次循环int tmp = cantor(root.board);if (visit[tmp])continue;elsevisit[tmp] = true;  //  判定当前路径长度是否超过限制,若是则跳过当次循环if (root.depth > limit)continue;  //  剪枝:判定当前路径的末尾是否出现“AA”或“BBBB”或“CCCC”的子串,若是则跳过当次循环if (root.path.length() >= 2&& (root.path[root.path.length() - 1] == 'A')&& (root.path[root.path.length() - 2] == 'A'))continue;if (root.path.length() >= 4&& (root.path[root.path.length() - 1] == 'B')&& (root.path[root.path.length() - 2] == 'B')&& (root.path[root.path.length() - 3] == 'B')&& (root.path[root.path.length() - 4] == 'B'))continue;if (root.path.length() >= 4&& (root.path[root.path.length() - 1] == 'C')&& (root.path[root.path.length() - 2] == 'C')&& (root.path[root.path.length() - 3] == 'C')&& (root.path[root.path.length() - 4] == 'C'))continue;  //  若root的状态恰好是目标状态,则输出结果并跳出循环使得函数返回  //  若非目标状态则依此将root经过A、B、C操作所得的状态推入队列if (root.board == target) {flag = true;cout << root.depth << ' ' << root.path << endl;return;}else {boardTree left = boardTree(operationA(root.board), (root.path + "A"), root.depth + 1);boardTree middle = boardTree(operationB(root.board), (root.path + "B"), root.depth + 1);boardTree right = boardTree(operationC(root.board), (root.path + "C"), root.depth + 1);treeQ.push(left);treeQ.push(middle);treeQ.push(right);}}}int main() {int limit;cin >> limit;  //  输入规定路径长度限制while (limit != -1) {char ch; string target = "";for (int i = 0; i < 8; i++) {  //  输入初始状态字符串cin >> ch;target += ch;}if (startPattern == target)  //  若初始状态等与目标状态,输出结果cout << 0 << endl;else {  //  否则构造出事魔板状态结构体,进行construct操作boardTree root = boardTree(startPattern, "", 0);queue<boardTree> treeQ;treeQ.push(root);memset(visit, false, PERMU);bool flag = false;construct(target, limit, flag, treeQ, visit);if (!flag)cout << "-1\n";}cin >> limit;}return 0;}


测试数据

 

0 0
原创粉丝点击