1. 程式人生 > >UVA - 1343 The Rotation Game (BFS/IDA*)

UVA - 1343 The Rotation Game (BFS/IDA*)

{} char cto 含義 its none vector color 還要

題目鏈接

紫書例題。

首先附上我第一次bfs+剪枝TLE的版本:

技術分享圖片
  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 }
View Code

這個版本TLE的原因是,如果直接bfs的話,總狀態數為$C(24,8)*C(16,8)=9465511770$,顯然太大了,即使加了剪枝狀態數依然很大,妥妥地TLE。

但如果預先假定一個數是正確答案,那麽剩下的兩個數就沒有區別了,所以狀態可以用0和1來表示,這樣總狀態數就縮小到了$C(24,8)=735471$,在可接受範圍內了。把原狀態分解成三個子狀態跑一遍bfs即可得到正確結果。但是如果對每個輸入都跑一遍bfs的話依然會TLE,而我們發現對於每組輸入,所有的狀態表示的含義都是一樣的,因此可以預先對末狀態跑一遍bfs,記錄下所有狀態到末狀態的最短距離,這樣每接受一組輸入都可以直接通過bfs樹得到路徑。

bfs的版本:(狀態的保存用了哈希表,用stl中的map應該也可以)

技術分享圖片
  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 }
View Code

另外一種方法是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步。)

技術分享圖片
 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 }
View Code

怎麽樣,代碼是不是比bfs的版本簡潔多了?而且速度出乎意料地比bfs還要快很多。

感覺IDA*就像暴搜+剪枝,bfs就像dp,具體該用哪個還得靠自己斟酌斟酌~~

UVA - 1343 The Rotation Game (BFS/IDA*)