P4289 [HAOI2008]移动玩具
广搜
4*4 的方阵只有 0 和 1
显然可以状态压缩
(如样例的开始状态压缩后就是1111000011100010)
为了加快速度用了双向广搜(顺便学了一下双向广搜)
双向广搜顾名思义
就是从起点和终点两个方向广搜
每次选择扩展步数少的扩展一层
然后一旦一个状态被两边都找到了
那就把两边的步数加一下,就是答案了
然后要注意位运算的细节
具体实现看代码吧
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<queue> using namespace std; struct node { int p,stp; };//广搜的队列,p表示状态,stp表示当前走了几步 queue <node> q[2]; bool pd[100007][2];//记忆化,pd[i][]为到i状态需要的最少步数 int ans,st,lst; inline void add(int k,int p,int stp) //尝试把状态p加入队列 { if(pd[p][k^1]||(p==st&&k)||(p==lst&&k==0))//如果另一边已经找过了或者到了另一边的起点 //因为在起点和终点的pd为0 所以要特判一下 { ans=stp+pd[p][k^1];//更新ans return;//直接返回 } if(pd[p][k]||p==st||p==lst) return;//如果找过了或者回到开始点,直接返回 //否则 pd[p][k]=stp;//更新pd node t; t.p=p; t.stp=stp; q[k].push(t);//加入队列 } inline void bfs() { int k= q[1].size()<q[0].size();//确定要从哪一边扩展 int now=q[k].front().stp; while(!q[k].empty()) { node t=q[k].front(); if(t.stp>now||ans) break;//保证一次只扩展一层 q[k].pop();//(细节)要先判断再弹出 for(int i=15;i>=0;i--)//向左移动 { if(i%4==3) continue;//注意如果在边界就不能动 if( !(t.p& (1<<i) ) || t.p& ( 1<<(i+1) ) ) continue;//判断 add(k, t.p^ (1<<i) ^ ( 1<<(i+1) ), t.stp+1);//直接异或一波得到下一步的状态 } for(int i=15;i>=0;i--)//向右 { if(i%4==0) continue;//同样判断 if( !(t.p& (1<<i) ) || t.p& (1<<(i-1) ) ) continue; add(k, t.p^ (1<<i) ^ ( 1<<(i-1) ), t.stp+1); }//同上 for(int i=11;i>=0;i--)//向上,注意i的范围 { if( !(t.p& (1<<i) ) || t.p& (1<<(i+4) ) ) continue; add(k, t.p^ (1<<i) ^ ( 1<<(i+4) ), t.stp+1); }//同上 for(int i=15;i>=4;i--)//向上,同样注意i { if( !(t.p& (1<<i) ) || t.p& (1<<(i-4) ) ) continue; add(k, t.p^ (1<<i) ^ ( 1<<(i-4) ), t.stp+1); }//同上 } } int main() { char ss[7]; memset(ss,0,sizeof(ss)); for(int i=0;i<4;i++) { cin>>ss; for(int j=0;j<4;j++) st+=( (ss[j]-'0')<<(15-i*4-j) ); }//读入状态 node t; t.p=st; t.stp=0; q[0].push(t);//开始状态压入队列 for(int i=0;i<4;i++) { cin>>ss; for(int j=0;j<4;j++) lst+=( (ss[j]-'0')<<(15-i*4-j) ); } t.p=lst; q[1].push(t);//同上 if(st==lst)//特判一波起点和终点状态相同的情况 { cout<<0; return 0; } while(!ans) bfs();//广搜找ans cout<<ans; }