Cubic Eight-Puzzle(UVA-1604)
网址如下:
Cubic Eight-Puzzle - UVA 1604 - Virtual Judge (vjudge.net)
(第三方网站)
AC了!本来以为会TLE的
因为当时已经把我能想到的优化方法都加上去了,可是对于深度有30及以上的样例,在我的电脑上运行也是需要差不多一秒的
但是上交之后只花了380ms,AC了
代码如下:
#include<cstdio>
#include<cctype>
#include<cstring>
#include<queue>
const int maxState = 7;
const int maxn = 3;
const int maxStep = 30;
struct Node{
int x, y, d;
int cube[3][3];
};
int nxtx[4]{-1, 1, 0, 0};//上,下,左,右
int nxty[4]{0, 0, -1, 1};
int hor[maxState]{0, 3, 6, 1, 5, 4, 2};//横向移动
int ver[maxState]{0, 5, 4, 6, 2, 1, 3};//纵向移动
int target[maxn][maxn];//目标颜色
char vis[maxState][maxState][maxState][maxState][maxState][maxState][maxState][maxState][maxState];
int x, y, t_x, t_y;
bool is_reached(const Node &u){
for(int i = 0; i < maxn; i++)
for(int j = 0; j < maxn; j++)
if(target[i][j] != (u.cube[i][j] + 1) / 2) return false;
return true;
}
int bfs(void){
Node base; base.x = x; base.y = y; base.d = 0;
for(int i = 0; i < maxn; i++)
for(int j = 0; j < maxn; j++)
base.cube[i][j] = 1;
base.cube[base.x][base.y] = 0;
std::queue<Node> q; q.push(base);
while(!q.empty()){
Node u = q.front(); q.pop();
//检查
if(is_reached(u)) return u.d;
if(vis[u.cube[0][0]][u.cube[0][1]][u.cube[0][2]][u.cube[1][0]][u.cube[1][1]][u.cube[1][2]][u.cube[2][0]][u.cube[2][1]][u.cube[2][2]] == 1) continue;
vis[u.cube[0][0]][u.cube[0][1]][u.cube[0][2]][u.cube[1][0]][u.cube[1][1]][u.cube[1][2]][u.cube[2][0]][u.cube[2][1]][u.cube[2][2]] = 1;
if(u.d > 21) return -1;
//下一步
for(int i = 0; i < 4; i++){
int _x = u.x + nxtx[i], _y = u.y + nxty[i];
if(_x < 0 || _x > 2 || _y < 0 || _y > 2) continue;
Node v = u;
v.x = _x; v.y = _y; v.cube[v.x][v.y] = 0; v.d = u.d + 1;
if(i < 2)
v.cube[u.x][u.y] = ver[u.cube[_x][_y]];//纵向移动
else
v.cube[u.x][u.y] = hor[u.cube[_x][_y]];//横向移动
q.push(v);
}
}
return 0;
}//正向bfs
void init_push(std::queue<Node> &q, Node &base, int i, int j){
base.cube[i][j] = target[i][j] * 2;
if(i == 2 && j == 2) q.push(base);
else init_push(q, base, (j + 1) == 3 ? i + 1 : i, (j + 1) % 3);
if(base.cube[i][j]){
base.cube[i][j] -= 1;
if(i == 2 && j == 2) q.push(base);
else init_push(q, base, (j + 1) == 3 ? i + 1 : i, (j + 1) % 3);
}
}
int rbfs(void){
Node base; base.x = t_x; base.y = t_y; base.d = 0;
std::queue<Node> q;
init_push(q, base, 0, 0);
while(!q.empty()){
Node u = q.front(); q.pop();
//检查
if(vis[u.cube[0][0]][u.cube[0][1]][u.cube[0][2]][u.cube[1][0]][u.cube[1][1]][u.cube[1][2]][u.cube[2][0]][u.cube[2][1]][u.cube[2][2]] == 1) return u.d + 21;
if(vis[u.cube[0][0]][u.cube[0][1]][u.cube[0][2]][u.cube[1][0]][u.cube[1][1]][u.cube[1][2]][u.cube[2][0]][u.cube[2][1]][u.cube[2][2]] == -1) continue;
vis[u.cube[0][0]][u.cube[0][1]][u.cube[0][2]][u.cube[1][0]][u.cube[1][1]][u.cube[1][2]][u.cube[2][0]][u.cube[2][1]][u.cube[2][2]] = -1;
if(u.d > 9) return -1;
//下一步
for(int i = 0; i < 4; i++){
int _x = u.x + nxtx[i], _y = u.y + nxty[i];
if(_x < 0 || _x > 2 || _y < 0 || _y > 2) continue;
Node v = u;
v.x = _x; v.y = _y; v.cube[v.x][v.y] = 0; v.d = u.d + 1;
if(i < 2)
v.cube[u.x][u.y] = ver[u.cube[_x][_y]];//纵向移动
else
v.cube[u.x][u.y] = hor[u.cube[_x][_y]];//横向移动
q.push(v);
}
}
return 0;
}//逆向bfs
void input(void){
char c;
for(int i = 0; i < maxn; i++)
for(int j = 0; j < maxn; ){
c = getchar();
switch(c)
{
case 'W':
target[i][j++] = 1;
break;
case 'B':
target[i][j++] = 2;
break;
case 'R':
target[i][j++] = 3;
break;
case 'E':
target[i][j] = 0;
t_x = i, t_y = j++;
break;
default:
break;
}
}
}
int main(void)
{
while(scanf("%d%d", &y, &x) == 2 && x){
memset(vis, 0, sizeof(vis)); x--; y--;
input();
int ans = bfs();
if(ans == -1) ans = rbfs();
printf("%d\n", ans);
}
return 0;
}
解释一下代码的一些方面:
首先是方块的状态表示:
0代表此处为空(E)
1代表正上方为W,正面为R,侧面为B
2代表正上方为W,正面为B,侧面为R
3代表正上方为B,正面为R,侧面为W
4代表正上方为B,正面为W,侧面为R
5代表正上方为R,正面为W,侧面为B
6代表正上方为R,正面为B,侧面为W
颜色和数字的关系:
0-E
1-W
2-B
3-R
则可以得出 方块正上方的颜色 = (方块的状态+1) / 2
(当然是用数字表示)
然后是方块转动后的变化:
int hor[maxState]{0, 3, 6, 1, 5, 4, 2};//横向移动
int ver[maxState]{0, 5, 4, 6, 2, 1, 3};//纵向移动
直接用数组记录下来了,下标代表原方块的状态
总的思路是bfs,用vis来记录已经出现过的状态(所有方块),但是为了缩短搜索的深度(总所周知,bfs的结点数量是随深度增加而指数级增长的(按最坏情况下一个结点可以扩展出4个结点))
所以可以用到双向bfs来把深度直接减少一半,其中,正向搜索的状态记录在vis中的值为1,逆向搜索的为-1
但是考虑到终点状态只给出了颜色,没给出方块状态,所以逆向bfs有两个思路:
1、直接用颜色表示,初始结点为1个,但是一个结点可以延申出8个结点
2、和正向bfs一样用方块状态表示,初始结点有2^8=256个,一个结点可以延申出4个结点
可以计算出在搜索深度在9以内的时候,法1的效率优于法2,但是考虑到法1在处理vis数组的时候更加麻烦,我是选择了法2,这样除了vis的值和初始结点不同之外,逆向搜索可以直接copy正向搜索
顺带一提,这里没有和普通的双向bfs一样,正向搜索一次,逆向搜索一次,也是考虑到了使用了法2的逆向搜索的初始结点太多,先尽量用正向搜索,最后再用逆向搜索
而且,可以注意到我的vis的数据类型为char,而不是int,是因为char可以当成整型来用,而且只需要1字节,即使是short也要占用2字节,就算是1字节,vis数组内存占用也有34MB左右
再谈谈我的思路的变化
我刚开始是想用二进制来进行优化的,因为如果加上未定义这个状态(实际上没什么鸟用),方块的状态有8个,可以用二进制的三个位来表示,而用一个int就可以表示9个方块一起的状态,妙啊!
而实际上,因为bfs在延申到下一个结点的时候需要将这个int值拆成9个值来计算,所以还不如直接用9个值来记录更快
而且,有一个致命的问题,若一个状态占一个字节,有8^9个状态,内存占用高达128MB,所以我没用vis数组,结果可想而知,运行超慢
后面是减了一个状态,就可以用上vis数组了,但是感觉还不是很快,就想到用双向bfs了
最后代码就改成了上面那样