• Sicily1151:魔板搜索及优化


    最终优化代码地址: https://github.com/laiy/Datastructure-Algorithm/blob/master/sicily/1151.c

    题目如下

    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

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

    第一行步数N不超过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

     

    此题难度不大,重要的是怎么优化,更快效率,更少内存使用。

    一开始看到这题,典型的搜索题目。

    没多想写出如下平凡的BFS搜索代码:

      1 #include <cstdio>
      2 #include <string>
      3 #include <iostream>
      4 #include <set>
      5 #include <queue>
      6 
      7 int n, destination, top, bottom, a, b, c, d;
      8 std::string ans;
      9 std::set<int> visited;
     10 
     11 struct State {
     12     int state;
     13     std::string step;
     14     State(int state, std::string step) {
     15         this->state = state;
     16         this->step = step;
     17     }
     18 };
     19 
     20 int OP_A(int state) {
     21     return state / 10000 + state % 10000 * 10000;
     22 }
     23 
     24 int OP_B(int state) {
     25     top = state / 10000;
     26     bottom = state % 10000;
     27     top = top % 10 * 1000 + top / 10;
     28     bottom = bottom % 10 * 1000 + bottom / 10;
     29     return top * 10000 + bottom;
     30 }
     31 
     32 int OP_C(int state) {
     33     a = state / 1000000 % 10;
     34     b = state / 100000 % 10;
     35     c = state / 100 % 10;
     36     d = state / 10 % 10;
     37 
     38     state -= a * 1000000;
     39     state -= b * 100000;
     40     state -= c * 100;
     41     state -= d * 10;
     42 
     43     state += c * 1000000;
     44     state += a * 100000;
     45     state += d * 100;
     46     state += b * 10;
     47 
     48     return state;
     49 }
     50 
     51 void bfs() {
     52     int state = 12348765;
     53     if (state == destination) {
     54         ans = "";
     55         return;
     56     }
     57 
     58     std::queue<State> q;
     59     q.push(State(state, ""));
     60     while (!q.empty()) {
     61         State s = q.front();
     62         q.pop();
     63 
     64         if (visited.find(s.state) != visited.end())
     65             continue;
     66         if (s.step.length() > (size_t)n)
     67             break;
     68         if (s.state == destination) {
     69             ans = s.step;
     70             break;
     71         }
     72 
     73         visited.insert(s.state);
     74 
     75         q.push(State(OP_A(s.state), s.step + "A"));
     76         q.push(State(OP_B(s.state), s.step + "B"));
     77         q.push(State(OP_C(s.state), s.step + "C"));
     78     }
     79 }
     80 
     81 int main() {
     82     while (scanf("%d", &n) && n != -1) {
     83 
     84         ans = "NOT FOUND";
     85         destination = 0;
     86         visited.clear();
     87 
     88         int temp[8], base = 1, i;
     89         for (i = 0; i < 8; i++)
     90             scanf("%d", temp + i);
     91         for (i = 7; i >= 0; i--)
     92             destination += temp[i] * base, base *= 10;
     93 
     94         bfs();
     95 
     96         if (ans == "NOT FOUND")
     97             printf("-1
    ");
     98         else
     99             std::cout << ans.length() << " " << ans << std::endl;
    100     }
    101     return 0;
    102 }

    轻轻松松过了,时间0.4s,内存2300kb。

    时间明显慢的不能忍,看排行榜少数人最佳的是0.00s!

    于是开始我们的优化过程:

    首先联想到,这里用的set数据结构来记录搜索中访问过的节点,而我们对set的使用基本不会涉及交集并集的操作,只是单纯的访问这个元素在不在。

    那么有个基础常识需要注意:STL中set是用红黑树实现的,此时对元素的查找是远不如哈希快的,所以我们把set替换成hash_set将会大幅提高效率。

    即把原来的set声明改成hash_set即可:

    1 __gnu_cxx::hash_set<int> visited;

    简单的改动, 时间0.25s,内存2428kb。

    还是不够快!

    于是继续修改,舍弃hash_set,改为visited数组线性访问,但是内存会爆! 8^8的空间!

    用康托展开减少记录空间,即8^8空间压到8!空间大小,此时内存不会爆。

    代码如下:

      1 #include <cstdio>
      2 #include <string>
      3 #include <iostream>
      4 #include <queue>
      5 #include <cstring>
      6 
      7 int n, destination, top, bottom, a, b, c, d, base, i, j;
      8 std::string ans;
      9 int fact[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320};
     10 bool visited[40321];
     11 
     12 struct State {
     13     int state;
     14     std::string step;
     15     State(int state, std::string step) {
     16         this->state = state;
     17         this->step = step;
     18     }
     19 };
     20 
     21 int encode(int n)
     22 {
     23     static int tmp[8];
     24     for (i = 7; i >= 0; i--) {
     25         tmp[i] = n % 10;
     26         n /= 10;
     27     }
     28 
     29     for (i = 0; i < 8; i++) {
     30         base = 0;
     31         for (j = i + 1; j < 8; j++)
     32             if (tmp[i] > tmp[j]) base++;
     33         n += fact[7 - i] * base;
     34     }
     35 
     36     return n;
     37 }
     38 
     39 int OP_A(int state) {
     40     return state / 10000 + state % 10000 * 10000;
     41 }
     42 
     43 int OP_B(int state) {
     44     top = state / 10000;
     45     bottom = state % 10000;
     46     top = top % 10 * 1000 + top / 10;
     47     bottom = bottom % 10 * 1000 + bottom / 10;
     48     return top * 10000 + bottom;
     49 }
     50 
     51 int OP_C(int state) {
     52     a = state / 1000000 % 10;
     53     b = state / 100000 % 10;
     54     c = state / 100 % 10;
     55     d = state / 10 % 10;
     56 
     57     state -= a * 1000000;
     58     state -= b * 100000;
     59     state -= c * 100;
     60     state -= d * 10;
     61 
     62     state += c * 1000000;
     63     state += a * 100000;
     64     state += d * 100;
     65     state += b * 10;
     66 
     67     return state;
     68 }
     69 
     70 void bfs() {
     71     int state = 12348765;
     72     if (state == destination) {
     73         ans = "";
     74         return;
     75     }
     76 
     77     std::queue<State> q;
     78     q.push(State(state, ""));
     79     while (!q.empty()) {
     80         State s = q.front();
     81         q.pop();
     82 
     83         if (visited[encode(s.state)])
     84             continue;
     85         if (s.step.length() > (size_t)n)
     86             break;
     87         if (s.state == destination) {
     88             ans = s.step;
     89             break;
     90         }
     91 
     92         visited[encode(s.state)] = true;
     93 
     94         q.push(State(OP_A(s.state), s.step + "A"));
     95         q.push(State(OP_B(s.state), s.step + "B"));
     96         q.push(State(OP_C(s.state), s.step + "C"));
     97     }
     98 }
     99 
    100 int main() {
    101     while (scanf("%d", &n) && n != -1) {
    102 
    103         ans = "NOT FOUND";
    104         destination = 0;
    105         memset(visited, 0, sizeof(visited));
    106 
    107         int temp[8], base = 1, i;
    108         for (i = 0; i < 8; i++)
    109             scanf("%d", temp + i);
    110         for (i = 7; i >= 0; i--)
    111             destination += temp[i] * base, base *= 10;
    112 
    113         bfs();
    114 
    115         if (ans == "NOT FOUND")
    116             printf("-1
    ");
    117         else
    118             std::cout << ans.length() << " " << ans << std::endl;
    119     }
    120     return 0;
    121 }

    时间0.22s,空间1404kb。

    效果并没有比hash_set快多少!

    那么问题出在哪里呢?

    博主想了一天(边上课边想),想到答案:

    每个样例都在搜索同一棵树!!!!!

    这样会产生大量的重复搜索,在不同的样例之间,特别是testcase很多的情况下,这是多余的。

    所以,我们要把整棵树记录下来。

    再看我们的数据结构,之前用的整数来存储一个节点状态,但是这里操作ABC效率是低下的,我们不如用3位来存储一个数字,这样ABC操作直接用位运算来完成。

    然后只遍历一次搜索树,把每个节点对应的操作记录下来,然后在具体需要的时候回溯逆向输出就是结果,这里考虑到用数组来记录节点状态的话内存开销太大1<<24的大小不是个小数字,所以用的是hash_map来记录树。

    代码如下:

     1 #include <cstdio>
     2 #include <queue>
     3 #include <hash_map>
     4 
     5 inline int OP_A(int &n) {
     6     return (n & 4095) << 12 | n >> 12;
     7 }
     8 
     9 inline int OP_B(int &n) {
    10     return (( 7 << 9 | 7 << 21 ) & n) >> 9 | (~(7 << 9 | 7 << 21) & n) << 3;
    11 }
    12 
    13 inline int OP_C(int &n) {
    14     return ((7 | 7 << 9 | 7 << 12 | 7 << 21) & n) | ((7 << 3) & n) << 3 | ((7 << 6) & n) << 12 | ((7 << 15) & n) >> 12 | ((7 << 18) & n) >> 3;
    15 }
    16 
    17 inline int resume_B(int &n) {
    18     return ((7 | 7 << 12) & n) << 9 | (~(7 | 7 << 12) & n) >> 3;
    19 }
    20 
    21 inline int resume_C(int &n) {
    22     return ((7 | 7 << 9 | 7 << 12 | 7 << 21) & n) | ((7 << 3) & n) << 12 | ((7 << 6) & n) >> 3 | ((7 << 15) & n) << 3 | ((7 << 18) & n) >> 12;
    23 }
    24 
    25 inline int zip(int *a) {
    26     static int code;
    27     code = 0;
    28     for (int i = 0; i < 8; ++i)
    29         code |= (a[i] - 1) << (3 * i);
    30     return  code;
    31 }
    32 
    33 int a[] = {1, 2, 3, 4, 8, 7, 6, 5}, origin = zip(a);
    34 
    35 __gnu_cxx::hash_map<int, char> record;
    36 
    37 void bfs() {
    38     int temp, code;
    39     std::queue<int> q;
    40     q.push(origin);
    41     while (!q.empty()) {
    42         code = q.front();
    43         q.pop();
    44         temp = OP_A(code);
    45         if (!record[temp])
    46             record[temp] = 'A', q.push(temp);
    47         temp = OP_B(code);
    48         if (!record[temp])
    49             record[temp] = 'B', q.push(temp);
    50         temp = OP_C(code);
    51         if (!record[temp])
    52             record[temp] = 'C', q.push(temp);
    53     }
    54 }
    55 
    56 int main() {
    57     bfs();
    58     int n, arr[8], i, j;
    59     char s[30];
    60     while (scanf("%d", &n) && n != -1) {
    61         for (i = 0; i < 8; i++)
    62             scanf("%d", arr + i);
    63         for (i = zip(arr), j = 0; i != origin && j < n; j++) {
    64             s[j] = record[i];
    65             switch (s[j]) {
    66                 case 'A':
    67                     i = OP_A(i);
    68                     break;
    69                 case 'B':
    70                     i = resume_B(i);
    71                     break;
    72                 case 'C':
    73                     i = resume_C(i);
    74                     break;
    75             }
    76         }
    77         if (i != origin)
    78             printf("-1
    ");
    79         else {
    80             printf("%d ", j);
    81             while (j--)
    82                 putchar(s[j]);
    83             putchar('
    ');
    84         }
    85     }
    86     return 0;
    87 }

    时间0.01s,空间1120kb。

    至于最优的0.00s,本人能力有限实在想不出来了,若有大神赐教定将顶礼膜拜。

  • 相关阅读:
    2016.5.15——leetcode:Number of 1 Bits ,
    2016.5.14——leetcode-HappyNumber,House Robber
    记录学习过程
    npm 模块安装机制简介
    搭建Vue.js开发环境(window10)
    pwd 显示当前所在的工作路径
    Lucene 6.5.0 入门Demo
    java.lang.UnsupportedClassVersionError
    window.onload 和 $(document).ready(function(){}) 的区别
    plsql + 客户端 连接oracle数据库
  • 原文地址:https://www.cnblogs.com/laiy/p/sicily_1151.html
Copyright © 2020-2023  润新知