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,本人能力有限实在想不出来了,若有大神赐教定将顶礼膜拜。

原文地址:https://www.cnblogs.com/laiy/p/sicily_1151.html