码迷,mamicode.com
首页 > 其他好文 > 详细

Sicily 1151 解题报告(魔板,广搜)

时间:2014-10-17 01:43:03      阅读:294      评论:0      收藏:0      [点我收藏+]

标签:style   blog   color   io   使用   ar   for   strong   sp   

 

    I.     原题中文大意

魔板由2*4个方块组成,用1到8的数字表示不同的块。 其初始状态是

               1 2 3 4

               8 7 6 5

对魔板可进行三种基本操作,这三种基本操作,可将任一种状态装换成另一种状态。

 

A (上下行互换)

B (行循环右移一格)

C (中间四块顺时针转90)

8 7 6 5

1 2 3 4

4 1 2 3

5 8 7 6

1 7 2 4

8 6 3 5

 

 II.       算法思想及解题用到的主要数据结构

  1. 广度优先搜索,已搜索过的节点不再进行记录,保证第一个找到的解即为最优解。
  2. HASH表,采用康拓展开表示序号数,共8!种可能性,无碰撞问题。

 

 III.       详细解题思路

  • BFS实现
    1. 将初始状态放入队列。
    2. 从队列中拿出队首元素,和目标状态比较,如果相等停止BFS。
    3. 若不等,对队首状态分别进行ABC三种操作,只把得到的新状态放入队列。若该状态已到达过,则忽略。
    4. 只要队列不为空,重复II III操作。

 

  • 康托展开?

康托展开是全排列和整数的双射, 即给定一个长为n的排列,康托展开得到该排列在全排列中的序号(0 ~ n!-1)。?例如 (2 1 3) 展开得 2, (1 2 3)展开得 0, (3 2 1)展开得5。 ?因此它能对n个数的排列进行状态的压缩和存储。例如要对8的全排列进行判重,没有必要开一个87654321?这么大的数组。对于一个n的排列数,通过康托展开得到这个排列在全部的排列中排第几位。这个序号最大?也就 8!=40320。根据序号就能通过hash快速确定 该状态是否访问过。

 

  • 以16进制掩码进行状态的存储及A、B、C操作

   一个魔板 8 个数,每个数都小于15,只需4bit就能表示,所以一个int 32位就能表示一个魔板。

   因为是4bit,恰好可以直接使用 16进制数 做位操作的掩码。

   初始状态对应的16进制数:                          12348765     

                                                            0x12348765?

   A操作: 上下行互换,就是高4位和低4位互换。 结果 0x87651234.

   B操作: 循环右移, 就是第4,8位左移3位,其他位右移1位。结果 0x41235876

   C操作: 中间顺时针转90度。 对比操作前后的结果 就知道哪些位要移动多少。        

                         0x12348765

                         0x17248635

   可以看出:第2位右移1位; 第3位右移4位, 第6位左移4位, 第7位左移1位。

 

 IV.       逐步求精算法描述(含过程及变量说明)

  • 变量:
/**
 * 魔板状态节点
 * @board 当前魔板状态, @pre 到达此状态的前一状态在队列中的位置,
 * @op 到达此状态的操作
 */
struct node{
    unsigned int board;
    unsigned short pre;
    char op;
};

/**
 * @fp 队头下标, @rp 队尾下标,
 * @q 广搜得到的所有序列, @isVisited 标记某序列是否访问过,
 * @QSIZE 队列大小,由前知为40320
 */
int fp = 0, rp = 1;
struct node q[QSIZE];
bool isVisited[QSIZE] = { false };

 

  • 方法
/**
 initialize 遍历所有8!个状态
 opa, opb, opc 三种操作,用函数指针循环调用
 cantor 计算康托展开
 isNew 判断是否是新的状态
 print 输出所求字符串
 */
void initialize();
unsigned opa( unsigned brd );
unsigned opb( unsigned brd );
unsigned opc( unsigned brd );
unsigned (*funcp[3])( unsigned ) = {opa, opb, opc};
int cantor( unsigned  brd );
int isNew( unsigned brd );
void print( node& x, int limit );

 

V.       程序注释清单(重要过程的说明)

  1 #include <cstdio>
  2 #include <string>
  3 #define QSIZE 40320
  4 using namespace std;
  5 
  6 struct node{
  7     unsigned int board; //  魔板序列
  8     unsigned short pre; //  前一个状态在q中的下标
  9     char op; //  操作
 10     /*
 11     void show() {  // 用于调试
 12         printf( "|%x   %c\n", board>>16, op );
 13         printf( " %x \n", board& 0xffff );
 14     }
 15     */
 16 };
 17 
 18 /**
 19  * @fp 队头下标, @rp 队尾下标,
 20  * @q 广搜得到的所有序列, @isVisited 标记某序列是否访问过,
 21  */
 22 int fp = 0, rp = 1;
 23 struct node q[QSIZE];
 24 bool isVisited[QSIZE] = { false };
 25 
 26 /**
 27  initialize 遍历所有8!个状态
 28  opa, opb, opc 三种操作
 29  cantor 计算康托展开
 30  isNew 判断是否是新的状态
 31  print 输出
 32  */
 33 void initialize();
 34 unsigned opa( unsigned brd );
 35 unsigned opb( unsigned brd );
 36 unsigned opc( unsigned brd );
 37 unsigned (*funcp[3])( unsigned ) = {opa, opb, opc}; //  函数指针,便于循环调用abc三种操作
 38 int cantor( unsigned  brd );
 39 int isNew( unsigned brd );
 40 void print( node& x, int limit );
 41 
 42 
 43 int main(){
 44     int n, i, temp;
 45     unsigned int target = 0;
 46     
 47     initialize();
 48     
 49     //  输入状态进行查找
 50     while(scanf("%d", &n) && (n != -1)){
 51         target = 0;
 52         for(i = 0; i < 8; ++i) { //  计算新状态对应16进制数
 53             scanf("%d", &temp);
 54             target <<= 4;
 55             target |= temp;
 56         }
 57         
 58         for(i = 0; i < QSIZE; i++) //  在队列中查找
 59             if(q[i].board == target) break;
 60         
 61         print( q[i], n );
 62     }
 63     
 64     return 0;
 65 }
 66 
 67 void initialize() {
 68     q->board = 0x12348765;
 69     q->op = 0;
 70     q->pre = 65535;
 71     isVisited[cantor(0x12348765)]=true;
 72     fp = 0, rp = 1;
 73     
 74     unsigned t;
 75     while(fp != rp){  //  队列不可能满,所以 fp!=rp是可行的
 76         //  对fp进行abc三种操作拓展,存入rp
 77         for(int i = 0; i < 3; ++i){
 78             if(isNew(t = funcp[i](q[fp].board))) {
 79                 // q[rp-1].show();
 80                 q[rp].board = t;
 81                 q[rp].op = A+i ;
 82                 q[rp].pre = fp;
 83                 ++rp;
 84             }
 85         }
 86         fp++;
 87     }
 88     //  printf("----------%d\n", rp );
 89     //  end
 90 }
 91 
 92 //  采用掩码进行ABC操作
 93 unsigned opa( unsigned brd ){
 94     return (brd << 16) | (brd >> 16);
 95 }
 96 unsigned opb( unsigned brd ){
 97     unsigned a = (brd & 0x000f000f) << 12, b = (brd & 0xfff0fff0) >> 4;
 98     return a | b;
 99 }
100 
101 unsigned opc( unsigned brd ){
102     unsigned a = (brd & 0x0f000000) >> 4,
103              b = (brd & 0x00f00000) >> 16,
104              c = (brd & 0x00000f00) << 16,
105              d = (brd & 0x000000f0) << 4;
106     
107     brd &= 0xf00ff00f;
108     brd |= a | b | c | d;
109     return brd;    
110 }
111 
112 int isNew( unsigned brd ){
113     int hash = cantor(brd);
114     if(isVisited[hash]) return 0;
115     
116     isVisited[hash] = true;
117     return 1;
118 }
119 
120 int cantor( unsigned brd ){
121     int i, j, t;
122     unsigned num=0, fac[8] = {0, 1, 2, 6, 24, 120, 720, 5040}, s[8];
123     
124     for(i = 0; i < 8; ++i ) s[i] = ( brd >> (4*i)) & 0xf;
125     num = (s[0] - 1) * 5040;
126     for(i = 1; i < 7; ++i) {
127         t = 0;
128         for (j = i + 1; j < 8; ++j)
129             if(s[j] < s[i])  ++t;
130         num += fac[7-i] * t;
131     }
132     return num;
133 }
134 
135 void print(node& x, int limit) {
136     int i, len = 0, str[23] = {x.op};
137     for(i = x.pre; i != 65535; i = q[i].pre){
138         str[++len] = q[i].op;
139     }
140     
141     if(len > limit)
142         printf("-1");
143     else {
144         printf("%d ", len);
145         for(i = len - 1; i >= 0; i--)
146             printf("%c", str[i] );
147     }
148     printf("\n");
149 }

 

VI.       测试数据(5-10组有梯度的测试数据,要考虑边界条件)

测试数据及答案:

 15
 5 8 2 3
 4 1 6 7
 -1
 
 25
 5 8 2 3
 4 1 6 7
 20  BBCBBCABCBCBBBCBCBCB
 
 20
 3 7 2 1
 6 5 8 4
 8 CCBBCABC
 
 20
 4 6 5 8
 1 3 7 2
 10 CCBBCABCAB
 
 30
 4 5 2 7  
 3 8 1 6
 22 ACBBBCBCBCBCBCABCABCBC

  

VII.     对时间复杂度,空间复杂度方面的分析、估算及程序优化的分析和改进

  • 空间消耗:

因为状态数目40320,所以空间复杂度就是常数。

但是节点的体积是可以再减小的,节点无需存储所有操作,只需存储当前操作(1B),还有父节点下标(2B).

每个节点只需 7个字节,由于存在字节对齐,所以每个节点会占用8字节。

总内存: 8*40320 + 312KB(运行时系统) = 634KB

采用这种方法,出乎意料的是 运行时间也减小了(0.01sec)。

整个搜索树是3叉的,对于第i层,要搜索3^i个节点(i从0开始)

通过判重来剪枝,可以显著减少搜索队列的长度。最后只留下 40320 个节点。 

  • 时间消耗:

采用位移的方法代替数组内部负值(如老师课堂所讲),可以减少时间开支;

状态节点使用一个char记录当前操作,一个int记录前一操作下表,代替用string记录到当前状态的所有操作以减小时空开支。

  •  可能的优化方案:

640KB的空间消耗可以接受,考虑减小时间消耗。

最直观的考虑可以优化哈希函数cantor()。可以使用非康托展开的其他哈希函数来减少操作量,但考虑到可能有碰撞问题,有可能需要开一个较大的数组,加大空间消耗,但参考其他同学的成果,使用1000kb左右内存时,时间消耗可以降至0.00sec。 

Sicily 1151 解题报告(魔板,广搜)

标签:style   blog   color   io   使用   ar   for   strong   sp   

原文地址:http://www.cnblogs.com/geminiinsysu/p/4030025.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!