sicily1151魔板

来源:互联网 发布:软件导刊是普刊吗 编辑:程序博客网 时间:2024/05/25 19:57

                     sicily1151 魔板解题报告

 

1. 原题中文大意

由8个大小相同方块组成的魔板,初始状态是1 2 3 4

                                         8 7 65

对魔板可以进行三种操作,分别是A操作:上下行互换,B操作:每次以行循环右移一个), C操作(中间四小块顺时钟转一格)。题目有多组测试数据,每组数据输入第一行是最多容许步数,第二三行是目标状态,输出为操作的步数及操作序列。

 

2. 算法思想及主要用到的数据结构

   这是一道典型的搜索题,给出一种初始状态,要求搜索到目标状态,可以用深度优先搜索或广度优先搜索来解决,即初始状态为搜索树的根节点,然后有ABC操作就有了三个孩子节点,每个孩子节点又有ABC操作,因此这是一颗完全三叉树,当扩展到目标状态时则这颗子树不用扩展下去了。如果是深度优先搜索的话,很可能要遍历整棵搜索树,考虑到问题的规模,用深搜不太妥当,肯定会超时。用广搜比较合适,但是如果只用单纯的广搜也很可能会超时,因为有很多状态会重复搜索,肯定会浪费很多时间,因此需要进行判重,当搜索要重复状态时,则不进行扩展。对于判重,可以用STL的set或者用康托展开,在搜索过程中,记录相关操作信息即可。

   用到的数据结构:主要用到队列,数组。

 

3. 详细解题思路

   可以存储每个状态对应的康托编码对应的操作序列来求解,即把所有状态先搜索完。

   首先定义一个数据结构,储存当前状态(用一个int型整数来表示)和操作序列(用string表示),再定义这样的一个结构体数组,大小为40321(8个数组成的8位数最多只有8!=40320个), 然后再定义一个bool型访问数组,记录哪个下标已被访问。接下来进行广搜,根节点为(12348765, ""), 把根节点放进队列,此时队列的头指针为0,尾指针为1,然后只要头指针小于尾指针,则取出队列元素,然后这个元素的一个整数分别进行A、B、C操作(这些操作可以通过对整数运算来实现,可写3个操作函数),如果操作后的整数的康托编码是没有访问过的,则构造一个新节点,新节点的操作序列为之前的操作序列再加上A或B或C, 知道队列里没有未访问的节点为止。当遇到新节点时,储存这个节点编码对应的操作序列。

最后,当程序输入时,求出这个目标状态对应的康托编码,然后输出答案或-1.

 

4. 逐步求精算法描述

定义结构体:

structStatus

{

    int num;

    string ans;

    Status(int s, string a)

    {

        num = s;

        ans = a;

    }

    Status(){}

}q[40321];  

q是结构体数组,最多只有40320种状态。

intfac[8] = { 1, 1, 2, 6, 24, 120, 720, 5040};

fac存阶乘大小,用于求康托编码;

boolvisit[40321];

visit[i]用于判断编码为i的状态是否已访问过;

stringans[40321];

ans[i]存编码为i的操作序列;

 

intA(int num) { // }  // A操作

intB(int num) { // } // B操作

intC(int num) { //}  // C操作

inthashCode(int num){ // return n; }

把状态为num进行康托展开,返回映射成的整数;

voidbfs()  // 广搜

{ }//把所有节点状态对应的操作序列储存起来

    

5. 逐程序注释清单

#include<cstdio>#include<cstring>#include<string>#include<set>#include<queue>#include<iostream>#include<algorithm> usingnamespace std; intn, target;inta[8];boolvisit[40321];//visit[i]用于判断编码为i的状态是否已访问过;stringans[40321];//ans[i]存编码为i的操作序列;//fac存阶乘大小,用于求康托编码;intfac[8] = { 1, 1, 2, 6, 24, 120, 720, 5040}; structStatus{    int num;      string ans;    Status(int s, string a)    {        num = s;        ans = a;    }    Status(){}}q[40321];//结构体数组,用于储存每种状态的数值和操作序列,最多有//40320种状态 intA(int num)   // A操作,上下行交换{    int t = 0;    t += num % 10000 * 10000;    t += num / 10000;    return t;} intB(int num)  //B操作,每行循环右移一位{    int t= 0;    t += num / 10000 % 10 * 10000000;    t += num / 100000 * 10000;    t += num % 10000 / 10;    t += num % 10 * 1000;    return t;} intC(int num)  //C操作,中间四小块顺时钟转一格{    int t = 0;    t += num / 10000000 * 10000000;    t += num / 1000 % 100 * 1000;    t += num % 10;    t += num / 100 % 10 * 1000000;    t += num % 100 / 10 * 100;    t += num / 100000 % 10 * 10;    t += num / 1000000 % 10 * 100000;    return t;}    /* 把数值为num映射成一个整数,比如12345678对应0,因为由1-8组成的8 *位数中12345678最小,问题转化为求解有多少个整数比这个数小,这就是康 *托展开*/inthashCode(int num){    int n, cnt, i;    n = 0; //1-8组成的8位数中比num小的数的个数    i = 7;    int a[8];    while(num != 0)//把整数num各个位的数值存在a数组中    {        a[i--] = num % 10;        num /= 10;    }    for(i = 0; i < 8; ++i)    {        cnt = 0; //cnt记录后面的数有多少个数比这个小        for(int j = i + 1; j < 8; ++j)        {            if(a[i] > a[j])                cnt++;        }        n += fac[7 - i] * cnt; //判断到当前位,有多少个数比num小}return n;} /* 利用队列把整个搜索树无重复的搜索遍历,记录每个编码对应的操作序列, */voidbfs()  // 广搜{    Status tem;    int t, code;    int head, tail;   memset(visit, false, sizeof(visit)); //首先设置所有的都未访问过     head = 0; //队列头指针    tail = 1; //队列尾指针    q[head] = Status(12348765, "");//根节点为(12348765,"")首先入队    visit[hashCode(q[head].num)] = true;    while(head < tail) //只要队列中还有未被访问过的元素    {        tem = q[head];  //取出队首元素         t = A(tem.num);  //t为操作A之后对应的数值        code = hashCode(t);//code为t对应的康托编码        if(!visit[code]){ //如果编码为code的状态为访问过,则标记已访//问,把操作序列存入ans[code], 并把这个状态节点入队            visit[code] = true;            ans[code] = tem.ans +"A";            q[tail++] =Status(t,tem.ans+"A");        }         t = B(tem.num);//t为操作A之后对应的数值        code = hashCode(t);//code为t对应的康托编码        if(!visit[code]){//如果编码为code的状态为访问过,则标记已访//问,把操作序列存入ans[code], 并把这个状态节点入队            visit[code] = true;            ans[code] = tem.ans +"B";            q[tail++] =Status(t,tem.ans+"B");        }         t = C(tem.num);//t为操作A之后对应的数值        code = hashCode(t);//code为t对应的康托编码        if(!visit[code]){//如果编码为code的状态为访问过,则标记已访//问,把操作序列存入ans[code], 并把这个状态节点入队            visit[code] = true;            ans[code] = tem.ans +"C";            q[tail++] =Status(t,tem.ans+"C");        }        head++; //头指针向后移一位    }} intmain(){    int i;    bfs();    while(scanf("%d", &n) != EOF&& n != -1)    {        for(i = 0; i < 8; ++i)        {            scanf("%d", a[i] + i);        }        int t = 1;        target = 0;        for(i = 7; i >= 0;--i)        {            target += t*a[i];            t *= 10;        }        int code = hashCode(target);        if(ans[code].size() > n)            cout << -1 << endl;        else            cout << ans[code].size()<< " " << ans[code] << endl;           }    return 0;}


 

 

6. 测试数据

   测试数据1: 1      1 2 3 4 8 7 6 5

   输出: 0

 

   测试数据2: 2      8 76 5 1 2 3 4

   输出: 1 A

 

   测试数据3: 3       1 3 6 4 8 2 7 5

   输出:  3 ACA

  

   测试数据4: 2       1 3 6 4 8 2 7 5

   输出:   -1

 

   测试数据5:  8       4 3 2 6 5 1 8 7

   输出:  5 ACBCA

 

7. 程序优化

  在上面的程序中,我使用了一次宽度优先搜索来记录搜索树上的所有状态,用康托编码来判重,这算是比较快的,因为用时0.04s,输入我用了scanf,但是输出我用了cout,这是因为我用了C++的string类型,如果我用C语言的char类型来储存操作序列的话,输出时间应该会减少,总时间应该会减到0.03,但是空间可能会增大。