sicily1151魔板

                     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,但是空间可能会增大。

 


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值