1151魔板B

来源:互联网 发布:温州公务员网络学堂 编辑:程序博客网 时间:2024/05/20 07:53

一.    题目描述:

Constraints                                                       

Time Limit: 1 secs, Memory Limit: 32 MB ,Special Judge

Description                                                       

魔板由8个大小相同方块组成,分别用涂上不同颜色,用18的数字表示。

其初始状态是

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                                                               

输入包括多个要求解的魔板,每个魔板用三行描述。

第一行步数NN肯能超过10),表示最多容许的步数。

第二、第三行表示目标状态,按照魔板的形状,颜色用18的表示。

N等于-1的时候,表示输入结束。

Output                                                            

对于每一个要求解的魔板,输出一行。

首先是一个整数M,表示你找到解答所需要的步数。接着若干个空格之后,从第一步开始按顺序给出M步操作(每一步是ABC),相邻两个操作之间没有任何空格。

注意:如果不能达到,则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)即可。

     


1 0
原创粉丝点击