P4289 [HAOI2008]移动玩具(bfs)
双向bfs+状态压缩+记忆化搜索
双向bfs用于对bfs的优化,每次找到可扩展节点少的一边进行一次bfs,找到的第一个互相接触的点即为最短路径
矩阵范围仅4*4大小,我们容易想到用二进制数压缩其状态,利于求解。
既然转成二进制,大小又<2^17,那么可以再加数组进行记忆化
不要忘了起点终点相同时的特判qwq
#include<iostream> #include<cstdio> #include<queue> #include<cstring> using namespace std; struct data{int zip,step;}; int ans,rec1[65538],rec2[65538]; //rec:记忆化用 queue <data> h[2]; //分别代表从起点/终点开始的bfs队列 inline void check(int to,int p,data x){ //检查该点是否符合条件 if(rec1[to]!=-1){ if(rec2[to]!=p) ans=rec1[to]+x.step+1; //如果该点已被对面搜到,那么已经得出最优解 }else{ rec1[to]=x.step+1,rec2[to]=p; h[p].push((data){to,x.step+1}); } } void output(int t){ //检查用,将10进制数转回4*4的二进制矩阵 cout<<"---\n"; int h[19]; for(int k=t,i=15;i>=0;--i) h[i]=k&1,k>>=1; for(int i=0;i<4;++i){ for(int j=i*4;j<=i*4+3;++j) cout<<h[j]; cout<<endl; } cout<<"---\n"; } inline void bfs(){ int p=(h[0].size()>h[1].size()),now=(h[p].front()).step; //找到可扩展节点少的一边,并且只扩展一层 while(!h[p].empty()){ data x=h[p].front(); if(x.step!=now||ans) break; h[p].pop(); int k=1,to; for(int i=1;i<65536;i<<=1,++k){ //用二进制数表示转移过程 if(!(x.zip&i)) continue; if(k%4!=0&&(!(x.zip&(i<<1)))){ //向左 to=x.zip-i+(i<<1); check(to,p,x); } if(k%4!=1&&(!(x.zip&(i>>1)))){ //向右 to=x.zip-i+(i>>1); check(to,p,x); } if(k>4&&(!(x.zip&(i>>4)))){ //向下 to=x.zip-i+(i>>4); check(to,p,x); } if(k<13&&(!(x.zip&(i<<4)))){ //向上 to=x.zip-i+(i<<4); check(to,p,x); } } } } int main(){ memset(rec1,-1,sizeof(rec1)); char q[6]; int tot1=0,tot2=0; for(int i=1;i<=4;++i){ //矩阵转成十进制数 cin>>q; for(int j=0;j<=3;++j) tot1+=q[j]-48,tot1<<=1; }tot1>>=1; //注意最后要右移一位 h[0].push((data){tot1,0}); rec1[tot1]=0; rec2[tot1]=0; for(int i=1;i<=4;++i){ cin>>q; for(int j=0;j<=3;++j) tot2+=q[j]-48,tot2<<=1; }tot2>>=1; h[1].push((data){tot2,0}); rec1[tot2]=0; rec2[tot2]=1; while(!ans&&tot1!=tot2) bfs(); //注意特判 printf("%d",ans); return 0; }