吴昊品游戏核心算法 Round 14 —— 推箱子的一种变式(三个箱子)
在更多的情况下,除了很多路障(墙壁)以外,我们会发现眼前的箱子绝对不止一个,只有一个箱子的可玩性绝对是不高的。但是,这里却又有一个显著的问题,就是如果箱子增加的话,问题的(至少是)空间复杂度会呈现指数级别地增加。
这里面考虑的因素就要复杂许多,但是,总思路还是不变的。我们这里采用分模块的方法来解决,源程序的模块性应该说是蛮好的。
1 注明头文件
2
3 #include<iostream>
4 //queue容器(这一次不用优先队列,而直接用队列了)
5 #include<queue>
6 using namespace std;
7
8 各种数据结构各种调
9
10 //这里定义一个点(x,y)
11 struct Point
12 {
13 int x,y;
14 };
15
16 //每一个Node代表一个状态
17 struct Node
18 {
19 Point you,box[3];
20 int step;
21 };
22
23 int n,m;
24 char map[8][8];
25 Node first,next;
26
27 //这里乃是八维数组,以及一个方向数组
28 bool hash[8][8][8][8][8][8][8][8];
29 int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
30
31 辅助函数A:设置某个结点的状态
32
33 //将最开始的状态定义为起始状态,并标记为true
34 void setHash(Node a)
35 {
36 hash[a.you.x][a.you.y][a.box[0].x][a.box[0].y][a.box[1].x][a.box[1].y][a.box[2].x][a.box[2].y]=true;
37 }
38
39 辅助函数B:获取某个结点的状态
40 //获取某个状态的布尔值
41 bool getHash(Node a)
42 {
43 return hash[a.you.x][a.you.y][a.box[0].x][a.box[0].y][a.box[1].x][a.box[1].y][a.box[2].x][a.box[2].y];
44 }
45
46 辅助函数C:抽取出来,越界和遇到墙都是不可以的
47
48 //这里拿出来,判断在这个位置是否是合理的,仍然有两种判断标准:(1)是否越界(2)是否有墙壁
49 bool checkPoint(Point a)
50 {
51 if(a.x>=0&&a.x<n&&a.y>=0&&a.y<m&&map[a.x][a.y]!='#')
52 {
53 return true;
54 }
55 return false;
56 }
57
58
59 辅助函数D:判断这个位置是否有一个箱子
60 //这里,判断出是否是一个箱子,而且还可以判断出箱子的编号(也就是这三个箱子的哪一个箱子)
61 int isBox(Point a)
62 {
63 for(int i=0;i<3;i++)
64 {
65 if(first.box[i].x==a.x&&first.box[i].y==a.y)
66 {
67 return i;
68 }
69 }
70 return -1;
71 }
72
73 辅助函数E:判断这个人是否可以移动这个位置的箱子
74
75 //判断这个人是否可以推动这个箱子,因为还存在两个以上的箱子叠合的情况,这样的话,就推不动了
76 bool isCanMove(Point a,int b,int di)
77 {
78 Point c;
79 //朝着那个人走的方向在前进一步,不过,原来那一步保证是下面有箱子的
80 c.x=a.x+dir[di][0];
81 c.y=a.y+dir[di][1];
82 //如果越界的话,当然就不行了
83 if(!checkPoint(c))
84 {
85 return false;
86 }
87 //如果不越界的话,那么对于三个箱子,扫描一遍
88 for(int i=0;i<3;i++)
89 {
90 //如果是现在的这个箱子,则continue
91 if(i==b)
92 {
93 continue;
94 }
95 //这里判断如果那个位置有另外一个箱子的话,则只能返回false了,因为人不可能同时推动两个箱子
96 else if(first.box[i].x==c.x&&first.box[i].y==c.y)
97 {
98 return false;
99 }
100 }
101 return true;
102 }
103
104
105 辅助函数F:判断游戏是否结束
106 bool isEnd(Node a)
107 {
108 //这里必须是三个箱子都在地图的叉叉位置上
109 if(map[a.box[0].x][a.box[0].y]=='@'&&map[a.box[1].x][a.box[1].y]=='@'&&map[a.box[2].x][a.box[2].y]=='@')
110 {
111 return true;
112 }
113 return false;
114 }
115
116
117 初始化函数:将所有的输入读入,并且初始化各种数据结构
118 void init()
119 {
120 //这个hash数组是八维的,其中的两维给小人,六维给三个箱子
121 memset(hash,0,sizeof(hash));
122 int i,j;
123 int k=0;
124 for(i=0;i<n;i++)
125 {
126 //这里很容易漏掉,由于读入的是字符型的变量,所以要将回车getchar()掉,不然会混到map的元素中
127 getchar();
128 for(j=0;j<m;j++)
129 {
130 //依次读入每个元素
131 scanf("%c",&map[i][j]);
132 if(map[i][j]=='X')
133 {
134 //小人的初始位置
135 first.you.x=i;
136 first.you.y=j;
137 }
138 else if(map[i][j]=='*')
139 {
140 //箱子的初始位置(因为有三个,这里加一个k变量作为下标)
141 first.box[k].x=i;
142 first.box[k++].y=j;
143 }
144 }
145 }
146 //这里设置步数为0,并且加入到Hash八维表中
147 first.step=0;
148 setHash(first);
149 }
150
151
152 广搜开始!
153 //从这里开始广搜
154 int bfs()
155 {
156 //定义一个队列结点(还优先队列本质上没多少区别,区别也莫过就是结构上的)
157 queue<Node>Q;
158 Q.push(first);
159 //开始完全搜索
160 while(!Q.empty())
161 {
162 first=Q.front();
163 Q.pop();
164 //判断整个游戏是否结束了
165 if(isEnd(first))
166 {
167 return first.step;
168 }
169 //朝着四个方向分别搜索
170 for(int i=0;i<4;i++)
171 {
172 next=first;
173 next.you.x=first.you.x+dir[i][0];
174 next.you.y=first.you.y+dir[i][1];
175 //这里提前走一步,因为有三个箱子,所以提前注明了
176 next.step=first.step+1;
177 //这里仍然是判断两个情况,一种是碰到箱子了,一种是没有碰到箱子
178 if(checkPoint(next.you))
179 {
180 //由专门的函数来判断是否是箱子
181 int k=isBox(next.you);
182 if(k!=-1)
183 {
184 //如果可以移动箱子的话,将箱子移动
185 if(isCanMove(next.you,k,i))
186 {
187 next.box[k].x+=dir[i][0];
188 next.box[k].y+=dir[i][1];
189 //如果还没有访问的话,这里标记,并进入队列
190 if(!getHash(next))
191 {
192 setHash(next);
193 Q.push(next);
194 }
195 }
196 }
197 else
198 {
199 //标记,但是箱子不进行移动
200 if(!getHash(next))
201 {
202 setHash(next);
203 Q.push(next);
204 }
205 }
206 }
207 }
208 }
209 //如果无解的话,这里返回-1(对于是否有解,也只能由计算机来判断了)
210 return -1;
211 }
212
213
214 主函数很精简,要的其实就是这个效果,辅助功能都有辅助函数来完成
215 int main()
216 {
217 while(scanf("%d%d",&n,&m)!=EOF)
218 {
219 //主函数很简洁,先初始化整个地图,然后就考虑输出了,输出的时候要调用bfs()函数
220 init();
221 printf("%d\n",bfs());
222 }
223 return 0;
224 }
225
226
2
3 #include<iostream>
4 //queue容器(这一次不用优先队列,而直接用队列了)
5 #include<queue>
6 using namespace std;
7
8 各种数据结构各种调
9
10 //这里定义一个点(x,y)
11 struct Point
12 {
13 int x,y;
14 };
15
16 //每一个Node代表一个状态
17 struct Node
18 {
19 Point you,box[3];
20 int step;
21 };
22
23 int n,m;
24 char map[8][8];
25 Node first,next;
26
27 //这里乃是八维数组,以及一个方向数组
28 bool hash[8][8][8][8][8][8][8][8];
29 int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
30
31 辅助函数A:设置某个结点的状态
32
33 //将最开始的状态定义为起始状态,并标记为true
34 void setHash(Node a)
35 {
36 hash[a.you.x][a.you.y][a.box[0].x][a.box[0].y][a.box[1].x][a.box[1].y][a.box[2].x][a.box[2].y]=true;
37 }
38
39 辅助函数B:获取某个结点的状态
40 //获取某个状态的布尔值
41 bool getHash(Node a)
42 {
43 return hash[a.you.x][a.you.y][a.box[0].x][a.box[0].y][a.box[1].x][a.box[1].y][a.box[2].x][a.box[2].y];
44 }
45
46 辅助函数C:抽取出来,越界和遇到墙都是不可以的
47
48 //这里拿出来,判断在这个位置是否是合理的,仍然有两种判断标准:(1)是否越界(2)是否有墙壁
49 bool checkPoint(Point a)
50 {
51 if(a.x>=0&&a.x<n&&a.y>=0&&a.y<m&&map[a.x][a.y]!='#')
52 {
53 return true;
54 }
55 return false;
56 }
57
58
59 辅助函数D:判断这个位置是否有一个箱子
60 //这里,判断出是否是一个箱子,而且还可以判断出箱子的编号(也就是这三个箱子的哪一个箱子)
61 int isBox(Point a)
62 {
63 for(int i=0;i<3;i++)
64 {
65 if(first.box[i].x==a.x&&first.box[i].y==a.y)
66 {
67 return i;
68 }
69 }
70 return -1;
71 }
72
73 辅助函数E:判断这个人是否可以移动这个位置的箱子
74
75 //判断这个人是否可以推动这个箱子,因为还存在两个以上的箱子叠合的情况,这样的话,就推不动了
76 bool isCanMove(Point a,int b,int di)
77 {
78 Point c;
79 //朝着那个人走的方向在前进一步,不过,原来那一步保证是下面有箱子的
80 c.x=a.x+dir[di][0];
81 c.y=a.y+dir[di][1];
82 //如果越界的话,当然就不行了
83 if(!checkPoint(c))
84 {
85 return false;
86 }
87 //如果不越界的话,那么对于三个箱子,扫描一遍
88 for(int i=0;i<3;i++)
89 {
90 //如果是现在的这个箱子,则continue
91 if(i==b)
92 {
93 continue;
94 }
95 //这里判断如果那个位置有另外一个箱子的话,则只能返回false了,因为人不可能同时推动两个箱子
96 else if(first.box[i].x==c.x&&first.box[i].y==c.y)
97 {
98 return false;
99 }
100 }
101 return true;
102 }
103
104
105 辅助函数F:判断游戏是否结束
106 bool isEnd(Node a)
107 {
108 //这里必须是三个箱子都在地图的叉叉位置上
109 if(map[a.box[0].x][a.box[0].y]=='@'&&map[a.box[1].x][a.box[1].y]=='@'&&map[a.box[2].x][a.box[2].y]=='@')
110 {
111 return true;
112 }
113 return false;
114 }
115
116
117 初始化函数:将所有的输入读入,并且初始化各种数据结构
118 void init()
119 {
120 //这个hash数组是八维的,其中的两维给小人,六维给三个箱子
121 memset(hash,0,sizeof(hash));
122 int i,j;
123 int k=0;
124 for(i=0;i<n;i++)
125 {
126 //这里很容易漏掉,由于读入的是字符型的变量,所以要将回车getchar()掉,不然会混到map的元素中
127 getchar();
128 for(j=0;j<m;j++)
129 {
130 //依次读入每个元素
131 scanf("%c",&map[i][j]);
132 if(map[i][j]=='X')
133 {
134 //小人的初始位置
135 first.you.x=i;
136 first.you.y=j;
137 }
138 else if(map[i][j]=='*')
139 {
140 //箱子的初始位置(因为有三个,这里加一个k变量作为下标)
141 first.box[k].x=i;
142 first.box[k++].y=j;
143 }
144 }
145 }
146 //这里设置步数为0,并且加入到Hash八维表中
147 first.step=0;
148 setHash(first);
149 }
150
151
152 广搜开始!
153 //从这里开始广搜
154 int bfs()
155 {
156 //定义一个队列结点(还优先队列本质上没多少区别,区别也莫过就是结构上的)
157 queue<Node>Q;
158 Q.push(first);
159 //开始完全搜索
160 while(!Q.empty())
161 {
162 first=Q.front();
163 Q.pop();
164 //判断整个游戏是否结束了
165 if(isEnd(first))
166 {
167 return first.step;
168 }
169 //朝着四个方向分别搜索
170 for(int i=0;i<4;i++)
171 {
172 next=first;
173 next.you.x=first.you.x+dir[i][0];
174 next.you.y=first.you.y+dir[i][1];
175 //这里提前走一步,因为有三个箱子,所以提前注明了
176 next.step=first.step+1;
177 //这里仍然是判断两个情况,一种是碰到箱子了,一种是没有碰到箱子
178 if(checkPoint(next.you))
179 {
180 //由专门的函数来判断是否是箱子
181 int k=isBox(next.you);
182 if(k!=-1)
183 {
184 //如果可以移动箱子的话,将箱子移动
185 if(isCanMove(next.you,k,i))
186 {
187 next.box[k].x+=dir[i][0];
188 next.box[k].y+=dir[i][1];
189 //如果还没有访问的话,这里标记,并进入队列
190 if(!getHash(next))
191 {
192 setHash(next);
193 Q.push(next);
194 }
195 }
196 }
197 else
198 {
199 //标记,但是箱子不进行移动
200 if(!getHash(next))
201 {
202 setHash(next);
203 Q.push(next);
204 }
205 }
206 }
207 }
208 }
209 //如果无解的话,这里返回-1(对于是否有解,也只能由计算机来判断了)
210 return -1;
211 }
212
213
214 主函数很精简,要的其实就是这个效果,辅助功能都有辅助函数来完成
215 int main()
216 {
217 while(scanf("%d%d",&n,&m)!=EOF)
218 {
219 //主函数很简洁,先初始化整个地图,然后就考虑输出了,输出的时候要调用bfs()函数
220 init();
221 printf("%d\n",bfs());
222 }
223 return 0;
224 }
225
226