UVA - 1343 The Rotation Game (BFS/IDA*)
紫书例题。
首先附上我第一次bfs+剪枝TLE的版本:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 const int N=24+2,inf=0x3f3f3f3f; 6 const int b[][10]= { 7 {0,2,6,11,15,20,22}, 8 {1,3,8,12,17,21,23}, 9 {10,9,8,7,6,5,4}, 10 {19,18,17,16,15,14,13}, 11 {23,21,17,12,8,3,1}, 12 {22,20,15,11,6,2,0}, 13 {13,14,15,16,17,18,19}, 14 {4,5,6,7,8,9,10}, 15 }; 16 const int md[]= {6,7,8,11,12,15,16,17}; 17 void rot(int* c,int x) { 18 const int* bb=b[x]; 19 for(int i=0; i<6; ++i)swap(c[bb[i]],c[bb[i+1]]); 20 } 21 void enc(int* c,ll& x) { 22 x=0; 23 for(int i=23; i>=0; --i)x=x*3+c[i]; 24 } 25 void dec(int* c,ll x) { 26 for(int i=0; i<24; ++i)c[i]=0; 27 for(int i=0; i<24; ++i)c[i]=x%3,x/=3; 28 } 29 int ok(int* c) { 30 for(int i=0; i<7; ++i)if(c[md[i]]!=c[md[i+1]])return false; 31 return true; 32 } 33 map<ll,int> d; 34 vector<ll> t; 35 int c[N],cc[N],s[N],mi; 36 ll ss; 37 38 void bfs1() { 39 mi=inf; 40 d.clear(); 41 t.clear(); 42 queue<ll> q; 43 q.push(ss),d[ss]=0; 44 while(!q.empty()) { 45 ll u=q.front(); 46 q.pop(); 47 dec(c,u); 48 if(ok(c)) { 49 mi=min(mi,d[u]); 50 t.push_back(u); 51 } 52 if(d[u]>=mi)continue; 53 for(int i=0; i<8; ++i) { 54 memcpy(cc,c,sizeof c); 55 rot(cc,i); 56 ll v; 57 enc(cc,v); 58 if(!d.count(v)) { 59 d[v]=d[u]+1; 60 q.push(v); 61 } 62 } 63 } 64 } 65 66 void bfs2() { 67 d.clear(); 68 queue<ll> q; 69 for(ll i:t)q.push(i),d[i]=0; 70 while(!q.empty()) { 71 ll u=q.front(); 72 q.pop(); 73 if(d[u]==mi)continue; 74 dec(c,u); 75 for(int i=0; i<8; ++i) { 76 memcpy(cc,c,sizeof c); 77 rot(cc,i); 78 ll v; 79 enc(cc,v); 80 if(!d.count(v)) { 81 d[v]=d[u]+1; 82 q.push(v); 83 } 84 } 85 } 86 } 87 88 void prans() { 89 ll u; 90 for(u=ss; d[u];) { 91 dec(c,u); 92 for(int i=0; i<8; ++i) { 93 memcpy(cc,c,sizeof c); 94 rot(cc,i); 95 ll v; 96 enc(cc,v); 97 if(d.count(v)&&d[v]==d[u]-1) { 98 printf("%c",i+'A'); 99 u=v; 100 } 101 } 102 } 103 dec(c,u); 104 printf("\n%d\n",c[md[0]]+1); 105 } 106 107 int input() { 108 for(int i=0; i<24; ++i)if(scanf("%d",&s[i])!=1)return 0; 109 return 1; 110 } 111 112 int main() { 113 while(input()) { 114 for(int i=0; i<24; ++i)s[i]--; 115 enc(s,ss); 116 bfs1(); 117 bfs2(); 118 prans(); 119 } 120 return 0; 121 }
这个版本TLE的原因是,如果直接bfs的话,总状态数为$C(24,8)*C(16,8)=9465511770$,显然太大了,即使加了剪枝状态数依然很大,妥妥地TLE。
但如果预先假定一个数是正确答案,那么剩下的两个数就没有区别了,所以状态可以用0和1来表示,这样总状态数就缩小到了$C(24,8)=735471$,在可接受范围内了。把原状态分解成三个子状态跑一遍bfs即可得到正确结果。但是如果对每个输入都跑一遍bfs的话依然会TLE,而我们发现对于每组输入,所有的状态表示的含义都是一样的,因此可以预先对末状态跑一遍bfs,记录下所有状态到末状态的最短距离,这样每接受一组输入都可以直接通过bfs树得到路径。
bfs的版本:(状态的保存用了哈希表,用stl中的map应该也可以)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 const int N=24+2,inf=0x3f3f3f3f; 6 const int b[][10]= { 7 {0,2,6,11,15,20,22}, 8 {1,3,8,12,17,21,23}, 9 {10,9,8,7,6,5,4}, 10 {19,18,17,16,15,14,13}, 11 {23,21,17,12,8,3,1}, 12 {22,20,15,11,6,2,0}, 13 {13,14,15,16,17,18,19}, 14 {4,5,6,7,8,9,10}, 15 }; 16 int t[]= {0,0,0,0,0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0,0,0,0}; 17 void rot(int* c,int x) { 18 const int* bb=b[x]; 19 for(int i=0; i<6; ++i)swap(c[bb[i]],c[bb[i+1]]); 20 } 21 void enc(int* c,int& x) { 22 x=0; 23 for(int i=23; i>=0; --i)x=x<<1|c[i]; 24 } 25 void dec(int* c,int x) { 26 for(int i=0; i<24; ++i)c[i]=0; 27 for(int i=0; i<24; ++i)c[i]=x&1,x>>=1; 28 } 29 struct Hashmap { 30 static const int N=1e6+10; 31 static const int mod=1e6+3; 32 int hd[mod],nxt[N],tot,key[N],val[N]; 33 int H(int x) {return (x+233)%mod;} 34 void clear() {tot=0; memset(hd,-1,sizeof hd);} 35 int count(int x) { 36 for(int u=hd[H(x)]; ~u; u=nxt[u])if(key[u]==x)return 1; 37 return 0; 38 } 39 int& operator[](int x) { 40 int h=H(x); 41 for(int u=hd[h]; ~u; u=nxt[u])if(key[u]==x)return val[u]; 42 nxt[tot]=hd[h],key[tot]=x,val[tot]=0,hd[h]=tot; 43 return val[tot++]; 44 } 45 } d; 46 47 int c[N],cc[N],s[N],ss,tt,mi,ans; 48 string str1,str2; 49 50 void bfs() { 51 d.clear(); 52 queue<int> q; 53 q.push(tt),d[tt]=0; 54 while(!q.empty()) { 55 int u=q.front(); 56 q.pop(); 57 dec(c,u); 58 for(int i=0; i<8; ++i) { 59 memcpy(cc,c,sizeof c); 60 rot(cc,i); 61 int v; 62 enc(cc,v); 63 if(!d.count(v)) { 64 d[v]=d[u]+1; 65 q.push(v); 66 } 67 } 68 } 69 } 70 71 int input() { 72 for(int i=0; i<24; ++i)if(scanf("%d",&s[i])!=1)return 0; 73 return 1; 74 } 75 76 int main() { 77 enc(t,tt); 78 bfs(); 79 while(input()) { 80 mi=inf; 81 for(int i=1; i<=3; ++i) { 82 for(int j=0; j<24; ++j)c[j]=s[j]==i?1:0; 83 int u; 84 enc(c,u); 85 mi=min(mi,d[u]); 86 } 87 str1="Z"; 88 for(int i=1; i<=3; ++i) { 89 for(int j=0; j<24; ++j)c[j]=s[j]==i?1:0; 90 int u; 91 enc(c,u); 92 if(d[u]==mi) { 93 str2.clear(); 94 while(u!=tt) { 95 dec(c,u); 96 for(int j=0; j<8; ++j) { 97 memcpy(cc,c,sizeof c); 98 rot(cc,j); 99 int v; 100 enc(cc,v); 101 if(d.count(v)&&d[v]==d[u]-1) { 102 str2.push_back(j+'A'); 103 u=v; 104 break; 105 } 106 } 107 } 108 if(str2<str1)str1=str2,ans=i; 109 } 110 } 111 if(mi==0)printf("No moves needed\n"); 112 else printf("%s\n",str1.c_str()); 113 printf("%d\n",ans); 114 } 115 return 0; 116 }
另外一种方法是IDA*。看了看网上的题解,基本都是IDA*的做法。IDA*还是很有参考价值的。
设g(n)为从初始状态到当前状态n所需步数,h(n)为当前状态n到目标状态至少所需步数,则g(n)+h(n)>maxdep时剪枝。显然h(n)可以用中心格点中1,2,3中的最大个数与8的差来表示,这样就不用保存状态,直接搜就行了。IDA*特别适用于这种bfs树的宽度较大而深度较小的搜索问题。(可以用bfs跑一遍试试,会发现在用01状态表示法的情况下,最坏情况下达到目标状态也仅需14步。)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 3 using namespace std; 4 typedef long long ll; 5 const int N=24+2,inf=0x3f3f3f3f; 6 const int b[][10]= { 7 {0,2,6,11,15,20,22}, 8 {1,3,8,12,17,21,23}, 9 {10,9,8,7,6,5,4}, 10 {19,18,17,16,15,14,13}, 11 {23,21,17,12,8,3,1}, 12 {22,20,15,11,6,2,0}, 13 {13,14,15,16,17,18,19}, 14 {4,5,6,7,8,9,10}, 15 }; 16 const int md[]= {6,7,8,11,12,15,16,17}; 17 void rot(int* c,int x,int f) { 18 const int* bb=b[x]; 19 if(f==1)for(int i=0; i<6; ++i)swap(c[bb[i]],c[bb[i+1]]); 20 else for(int i=5; i>=0; --i)swap(c[bb[i]],c[bb[i+1]]); 21 } 22 int count(int* c) { 23 int cnt[3]= {}; 24 for(int i=0; i<8; ++i)cnt[c[md[i]]-1]++; 25 return max(cnt[0],max(cnt[1],cnt[2])); 26 } 27 int s[N],ans; 28 char str[N]; 29 30 bool dfs(int dep,int mxd) { 31 int cnt=count(s); 32 if(cnt==8) {ans=s[md[0]]; str[dep]='\0'; return 1;} 33 if(8-cnt>mxd-dep)return 0; 34 for(int i=0; i<8; ++i) { 35 rot(s,i,1); 36 if(dfs(dep+1,mxd)) {str[dep]=i+'A'; return 1;} 37 rot(s,i,-1); 38 } 39 return 0; 40 } 41 42 void IDA() { 43 for(int mxd=0;; ++mxd)if(dfs(0,mxd))break; 44 if(str[0])puts(str); 45 else puts("No moves needed"); 46 printf("%d\n",ans); 47 } 48 49 int input() { 50 for(int i=0; i<24; ++i)if(scanf("%d",&s[i])!=1)return 0; 51 return 1; 52 } 53 54 int main() { 55 while(input())IDA(); 56 return 0; 57 }
怎么样,代码是不是比bfs的版本简洁多了?而且速度出乎意料地比bfs还要快很多。
感觉IDA*就像暴搜+剪枝,bfs就像dp,具体该用哪个还得靠自己斟酌斟酌~~