1151魔板B
来源:互联网 发布:温州公务员网络学堂 编辑:程序博客网 时间:2024/05/20 07:53
一. 题目描述:
Constraints
Time Limit: 1 secs, Memory Limit: 32 MB ,Special Judge
Description
魔板由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
用上述三种基本操作,可将任一种状态装换成另一种状态。
Input
输入包括多个要求解的魔板,每个魔板用三行描述。
第一行步数N(N肯能超过10),表示最多容许的步数。
第二、第三行表示目标状态,按照魔板的形状,颜色用1到8的表示。
当N等于-1的时候,表示输入结束。
Output
对于每一个要求解的魔板,输出一行。
首先是一个整数M,表示你找到解答所需要的步数。接着若干个空格之后,从第一步开始按顺序给出M步操作(每一步是A、B或C),相邻两个操作之间没有任何空格。
注意:如果不能达到,则M输出-1即可。
Sample Input
4
5 8 7 6
4 1 2 3
3
8 7 6 5
1 2 3 4
-1
Sample Output
2 AB
1 A
评分:M超过N或者给出的操作不正确均不能得分。
Problem Source
ZSUACM Team Member
二. 算法思想及解题中用到的主要数据结构:
数据结构:
1.结构体,用于保存变化后的魔板状态和操作序列
struct status{ int current; string actions;};
2.队列,用于保存枚举时的状态status序列
queue<status> operations;
算法思想:利用广度优先遍历的方法,通过群举的方法,逐步寻找。
三. 详细解题思路
1. 计算首先从初始魔板状态(12348765)开始。将其压入操作状态队列
2. 先判断是否符合目标魔板的状态
3. 如果满足,就返回走过的步数和操作序列
4. 如果不满足,分别利用ABC三种操作进行变换
5. 将变换后的三种结果分别插入队列,将之前进行操作的魔板状态弹出队列
6. 在最大步数满足的条件下,取出队头元素,跳到2继续
对于ABC三种操作,可以分别取某位数然后重新划定位数,叠加即可。
四. 逐步求精算法描述(含过程及变量说明):
1.入队进行判重,防止时空消耗指数级增长。上述算法中,经过ABC操作后的魔板状态,未加判断就直接加入队列中,会导致存储很多重复和冗余,使后续的时间空间开销急剧膨胀(按照3的指数级别)。所以应该在入队前和队列中的元素进行判重的,以决定是否插入
2.记录访问状态,避免重复计算。在上面的初步算法中,每次将不符合目标状态的魔板状态从队列中弹出,会丢失一些计算结果,即使进行了1中的判重,但是前面有部分状态序列已经丢失,如果后面变换得到和丢失一样的序列(这种情况极易发生,比如经过两步变换会得到12345678,经过十几步甚至更多的步数也会得到12345678),会重新加入队列,这种重复计算和存储的情况也会浪费之前的计算结果。所以必须存储已经访问过和未访问过的状态。
3.使用康托展开进行哈希,加快状态查询速度。在进行第一和第二步的判重时,需要和操作状态一个个比较,才能判重,这样会使这种判重操作的复杂度和队列的长度成线性增长O(n),n为队列的长度。所以需要采取快速索引的办法,在这里使用康托展开,用康托展开数做状态存储数组的下标,这样就可以快速进行判断。
4.康托展开:康托展开是全排列和自然数之间的双射关系,通过排列中的元素大小和出现的位置,对某个排列在全排列中进行排位编号。计算公式如下:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!,其中a[i]为当前未出现的元素中是排在第几个(从0开始)
五. 程序注释清单(重要过程的说明)
#include <iostream>#include <queue>#include <memory.h>using namespace std;struct status{ int current; string actions;};//三种操作ABCint A(int number){ int temp = number; temp = number/1000%10*1e7 + number/100%10*1e6 + number/10%10*1e5 + number%10*1e4 + number/(10000000)%10*1e3 + number/1000000%10*1e2 + number/100000%10*10 + number/10000%10; return temp;} int B(int number){ int temp = number; temp = number/10000%10*1e7 + number/10000000%10*1e6 + number/1000000%10*1e5 + number/100000%10*1e4 + number%10*1e3 + number/1000%10*1e2 + number/100%10*10 +number/10%10; return temp; } int C(int number){ int temp = number; temp = number/10000000%10*1e7 + number/100%10*1e6 + number/1000000%10*1e5 + number/10000%10*1e4 + number/1000%10*1e3 + number/10%10*1e2 + number/100000%10*10 + number%10; return temp; }//康托展开编码int encode(int n){ int tmp[8]; int cnt; int fact[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320}; for (int i = 7; i >= 0; i--) { tmp[i] = n%10; n /= 10; } for (int i = 0; i < 7; i++) { cnt = 0; for (int j = i+1; j < 8; j++) if (tmp[i] > tmp[j]) cnt++; n += fact[8-i-1] * cnt; } return n;}//广度优先搜索void search(int max_step, int goal){ bool visited[45000]; memset(visited, false, sizeof(visited)); int start = 12348765; status point, next; point.current = start; point.actions = ""; queue<status> operations; operations.push(point); while(!operations.empty()){ point = operations.front(); operations.pop(); if(point.actions.size() > max_step){ cout << -1 << endl; return; } if(point.current == goal){ cout << point.actions.size() << " " << point.actions << endl; return; } next.current = A(point.current); next.actions = point.actions + "A"; if(!visited[encode(next.current)]){ visited[encode(next.current)] = true; operations.push(next); } next.current = B(point.current); next.actions = point.actions + "B"; if(!visited[encode(next.current)]){ visited[encode(next.current)] = true; operations.push(next); } next.current = C(point.current); next.actions = point.actions + "C"; if(!visited[encode(next.current)]){ visited[encode(next.current)] = true; operations.push(next); } }}int main(){ int temp[8]; int max_step; int goal; while (cin >> max_step && max_step != -1) { for (int i = 0; i < 8; i++) cin >> temp[i];//将得到的魔板转换成int类型的目标序列 goal = temp[0]*1e7 +temp[1]*1e6 + temp[2]*1e5 + temp[3]*1e4 + temp[4]*1e3 + temp[5]*1e2 + temp[6]*1e1 + temp[7]; search(max_step, goal); } return 0;}
六. 测试数据(5-10组有剃度的测试数据,要考虑边界条件)
Case 1:边界测试
Case 2:负功能测试(步数不够)
Case 3:正功能测试(步数极少)
Case 4:功能测试
Case 5:功能测试
Case 6:功能测试
Case 7:结束条件测试:
Case 8: 非法输入测试
程序欠缺,需要完善对输入的检查
七. 对时间复杂度,空间复杂度方面的分析、估算及程序优化的分析和改进
由于在入队前进行了剪枝判重和状态记录,所以总的存储空间为常数级别,40320个节点。
时间复杂度,如果没有经过任何优化,时间复杂度会是3的指数级别,但是经过了剪枝和判重,最后的搜索都是在常数空间内进行,所以算法复杂度也是常数级别,最深的一支树的深度为40320,但是只是遍历这样的某几枝,所以时间复杂度同样是常数级别。
改进:对于状态序列的存储,是以空间换时间的做法,其实可以改进,只存储当前操作(1B)和父节点(2B 2^16 = 65536 > 40320)即可。