1. 程式人生 > >P4289 [HAOI2008]移動玩具

P4289 [HAOI2008]移動玩具

col eof oid 轉移 put 短路徑 註意 www 矩陣

P4289 [HAOI2008]移動玩具

雙向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; }

P4289 [HAOI2008]移動玩具