POJ - 1324 Holedox Moving (状态压缩+BFS/A*)
有一个n*m(1<=n,m<=20)的网格图,图中有k堵墙和有一条长度为L(L<=8)的蛇,蛇在移动的过程中不能碰到自己的身体。求蛇移动到点(1,1)所需的最小步数。
显然用8个(x,y)来表示蛇的状态是不现实的(用哈希也很难存下,要么爆内存,要么超时),所以首先应当进行状态压缩。可以发现蛇的身体是连续的,因此可以用一个表示方向的向量来储存蛇身体的每个部分在它上个部分的哪个方向(只有头部用(x,y)表示),这样总状态数就变成了n*m*(2^((L-1)*2)),在可接受范围内了。
可优化的部分:
1.可以在图的边界上放上一层墙,避免出界判定。
2.如果在碰撞判定时把“蛇身”和“墙”等同的话,为了防止在撤销蛇身所造成的影响的同时把墙也一并消除,可以用b[x][y]++,--的方法来代替b[x][y]=1。
AC代码:(编码和解码真是个体力活)
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 using namespace std; 5 typedef long long ll; 6 const int N=20+2; 7 const int dx[]= {0,0,-1,1}; 8 const int dy[]= {-1,1,0,0}; 9 struct P {int x,y;} p[N],r[N]; 10 int n,m,l,k,b[N][N],d[N][N][(1<<12)+10],S,ka; 11 struct D {int x,y,S;}; 12 13 int bfs() { 14 queue<D> q; 15 memset(d,-1,sizeof d); 16 d[p[0].x][p[0].y][S]=0,q.push({p[0].x,p[0].y,S}); 17 while(!q.empty()) { 18 int x=q.front().x,y=q.front().y,S=q.front().S; 19 q.pop(); 20 if(x==1&&y==1)return d[x][y][S]; 21 r[0]= {x,y}; 22 for(int i=1; i<l; ++i) { 23 int t=S>>((l-i-1)*2)&3; 24 r[i]= {r[i-1].x+dx[t],r[i-1].y+dy[t]}; 25 } 26 for(int i=0; i<l; ++i)b[r[i].x][r[i].y]++; 27 for(int i=0; i<4; ++i) { 28 int xx=x+dx[i],yy=y+dy[i],SS=S>>2|((i^1)<<((l-2)*2)); 29 if(!b[xx][yy]&&!~d[xx][yy][SS])d[xx][yy][SS]=d[x][y][S]+1,q.push({xx,yy,SS}); 30 } 31 for(int i=0; i<l; ++i)b[r[i].x][r[i].y]--; 32 } 33 return -1; 34 } 35 36 int main() { 37 while(scanf("%d%d%d",&n,&m,&l)&&n) { 38 printf("Case %d: ",++ka); 39 for(int i=0; i<l; ++i)scanf("%d%d",&p[i].x,&p[i].y); 40 S=0; 41 for(int i=1; i<l; ++i) 42 for(int j=0; j<4; ++j)if(p[i-1].x+dx[j]==p[i].x&&p[i-1].y+dy[j]==p[i].y) { 43 S=S<<2|j; 44 break; 45 } 46 memset(b,0,sizeof b); 47 for(int i=0; i<=n+1; ++i)b[i][0]=b[i][m+1]=1; 48 for(int i=0; i<=m+1; ++i)b[0][i]=b[n+1][i]=1; 49 scanf("%d",&k); 50 while(k--) { 51 int x,y; 52 scanf("%d%d",&x,&y); 53 b[x][y]=1; 54 } 55 printf("%d\n",bfs()); 56 } 57 return 0; 58 }
也可以进一步用A*优化,以每个点到点(1,1)的距离为估价函数进行转移,速度大幅提升。
一般A*中优先队列的结点要额外设一个属性g来表示已走过的距离,但也可以省去这个g,用d[x][y][S]来代替g,这样如果要把bfs改成A*的话,只要写出求每个点到点(1,1)距离的bfs代码,然后把原来的bfs代码中的queue换成priority_queue,front换成top就ok了,非常方便。
另外可以优化的一个地方是如果一个点不能到达(1,1),那么直接返回-1就行,不用再浪费时间跑A*了。
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 using namespace std; 5 typedef long long ll; 6 const int N=20+2; 7 const int dx[]= {0,0,-1,1}; 8 const int dy[]= {-1,1,0,0}; 9 struct P {int x,y;} p[N],r[N]; 10 int n,m,l,k,b[N][N],d[N][N][(1<<12)+10],S,h[N][N],ka; 11 struct D { 12 int x,y,S; 13 bool operator<(const D& b)const {return d[x][y][S]+h[x][y]>d[b.x][b.y][b.S]+h[b.x][b.y];} 14 }; 15 16 void bfs() { 17 queue<P> q; 18 memset(h,-1,sizeof h); 19 h[1][1]=0,q.push({1,1}); 20 while(!q.empty()) { 21 int x=q.front().x,y=q.front().y; 22 q.pop(); 23 for(int i=0; i<4; ++i) { 24 int xx=x+dx[i],yy=y+dy[i]; 25 if(!~h[xx][yy]&&!b[xx][yy])h[xx][yy]=h[x][y]+1,q.push({xx,yy}); 26 } 27 } 28 } 29 30 int Astar() { 31 if(!~h[p[0].x][p[0].y])return -1; 32 priority_queue<D> q; 33 memset(d,-1,sizeof d); 34 d[p[0].x][p[0].y][S]=0,q.push({p[0].x,p[0].y,S}); 35 while(!q.empty()) { 36 int x=q.top().x,y=q.top().y,S=q.top().S; 37 q.pop(); 38 if(x==1&&y==1)return d[x][y][S]; 39 r[0]= {x,y}; 40 for(int i=1; i<l; ++i) { 41 int t=S>>((l-i-1)*2)&3; 42 r[i]= {r[i-1].x+dx[t],r[i-1].y+dy[t]}; 43 } 44 for(int i=0; i<l; ++i)b[r[i].x][r[i].y]++; 45 for(int i=0; i<4; ++i) { 46 int xx=x+dx[i],yy=y+dy[i],SS=S>>2|((i^1)<<((l-2)*2)); 47 if(!b[xx][yy]&&!~d[xx][yy][SS])d[xx][yy][SS]=d[x][y][S]+1,q.push({xx,yy,SS}); 48 } 49 for(int i=0; i<l; ++i)b[r[i].x][r[i].y]--; 50 } 51 return -1; 52 } 53 54 int main() { 55 while(scanf("%d%d%d",&n,&m,&l)&&n) { 56 printf("Case %d: ",++ka); 57 for(int i=0; i<l; ++i)scanf("%d%d",&p[i].x,&p[i].y); 58 S=0; 59 for(int i=1; i<l; ++i) 60 for(int j=0; j<4; ++j)if(p[i-1].x+dx[j]==p[i].x&&p[i-1].y+dy[j]==p[i].y) { 61 S=S<<2|j; 62 break; 63 } 64 memset(b,0,sizeof b); 65 for(int i=0; i<=n+1; ++i)b[i][0]=b[i][m+1]=1; 66 for(int i=0; i<=m+1; ++i)b[0][i]=b[n+1][i]=1; 67 scanf("%d",&k); 68 while(k--) { 69 int x,y; 70 scanf("%d%d",&x,&y); 71 b[x][y]=1; 72 } 73 bfs(); 74 printf("%d\n",Astar()); 75 } 76 return 0; 77 }