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

题目链接

紫书例题。

首先附上我第一次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,具体该用哪个还得靠自己斟酌斟酌~~

posted @ 2019-02-09 11:42  jrltx  阅读(248)  评论(0编辑  收藏  举报