1.题目描述:点击打开链接
2.解题思路:本题是一道隐式图的搜索题目。一般来说,这类题目首先要定义状态,接下来是弄清楚状态如何转移,以及状态如何判重,如何判断当前状态是否和目标状态相同。至于求解最短路就是常用的BFS即可。接下来我们逐一展开讨论。
1.状态的定义:看到这道题,猛一下会想着把每个字符分别用01表示,然后看成二进制码进行状态压缩,这个状态定义虽然可以,但是显然,状态过于精确和复杂,如果把一行给压缩成一个整数,那么一个完整的图案要用8*9,即72个数才能描述。显然过于庞大,而且对于状态的转移也变得异常复杂。因此这不是一个好的定义。如果我们注意到每个方块的图案是刚性的这一特定,就会发现,其实重要的不是图案本身的样子,而是每个图案相对于初始状态中的图案旋转了多少次。也就是说,我们只需要用0~3即可准确地描述第i个方块相对于初始状态旋转的次数。这样,我们可以用9个,范围在0~3的整数来描述一个完整的图案。而且,我们发现,这样定义有一个非常大的好处就是旋转操作变得非常简单,如果是顺时针旋转就是0->1,1->2,2->3,3->0,逆时针反之。因此,这种定义可以作为一个好的定义。
2.状态的转移:根据题意描述,我们每次都要选择某一块将它顺时针旋转,然后,如果这个方块和相邻的方块之间有齿轮耦合,那么相邻的方块也会被带动旋转,以此类推。也就是,一个方块的旋转可能会带动多个方块同时旋转,看上去挺棘手的。这时我们可以考虑用一下常量数组来处理。因为我们在最初输入的时候还输入了每个方块上下左右是否具有齿轮。不过要注意,这个“左上右下”是针对初始状态来说的,那么如果我们把第i个方块顺时针旋转90度,此时,新状态的“左上右下”就是原初始状态中的“下左上右”。再旋转90度,就变成初始状态的“右下左上”,以此类推。即我们可以设一个状态转移数组来表示这种情况。用tp[i][j]表示当状态为i(此时的i取值范围是0~3)时,它的第j个下标对应的初始齿轮数组的下标应该是多少(下标从1开始,注意和状态的取值范围分开)。按照刚刚的讨论,tp[0][1]=1,tp[0][2]=2,tp[0][3]=3,tp[0][4]=4;tp[1][1]=4,tp[1][2]=1,tp[1][3]=2,tp[1][4]=3。
设置好了这个常量数组后,我们为了在O(1)时间内获得第i个方块的上下左右对应方块的编号,可以再设置4个常量数组。分别用up[i],down[i],lef[i],rig[i]来表示,这个数组不难写出。具备了这5个常量数组,rotate函数就非常好写了:首先让第i个方块顺时针旋转90度(即对应的值+1即可),然后用常量数组获得它上下左右的方块编号,用meshed函数判断这4个方块(如果存在的话)是否和第i个方块有齿轮耦合,有的话置1。接下来对于有齿轮耦合的,对它们进行rotate操作,同时用vis[i]来标记是否操作过。为了便于表示顺时针和逆时针,我们分别用1,2来表示。比如第i块旋转的编号是flag,那么跟它相邻的就变成3-flag。至于meshed函数,我们可以用上tp数组迅速获得相应的齿轮下标,来确定是否有耦合。这样,执行完rotate,就完成了状态转移。
3.状态的判重:我们发现,一共只有9个数,每个数的变化范围是0~3,因此不妨写一个Hash函数,将这9个数压缩成一个唯一的整数。我们发现,0~3占据了2个bit。那么只需要每次把一个数平移2i位即可。如果下标从1开始,就是2*(i-1)位(第一个数不用移动)。最多只需要平移16位即可。仍然在int范围之内。而且得到的Hash值一定不会出现重复,可以唯一的表示一个完整的状态。如果我们是通过d数组(BFS中的距离数组)来判重的,那么先初始化为INF,只需要看d[v](v是状态对应的Hash值)是否为INF即可实现判重。
4.是否到达目标状态:注意,这一步不要和状态判重混淆,因为本题的目标状态实际上可以用多对状态值去描述,而不可以简单的用Hash是否相同来判断。比如题目中的第5块方块,不管它如何旋转,图案都是一样的。也就是说,第5个状态值是0~3均可能是目标状态的一部分。这就提示我们需要另外找一种判断方法。那么如何做呢?根据状态的定义,我们可以首先把每一块都尝试旋转4次(不考虑齿轮耦合),看当前旋转的状态是否和目标状态中相应的方块图案完全一样(即这次我们需要用上输入的字符)。一样的话,就把它标记为1。如果我们用一个cor数组标记,那么cor[i][j]=1就表示第i个方块顺时针旋转j次后和目标状态的第i块图案一样。这样预处理后,判断函数就不难写了。只需要看对所有的i,是否有cor[i][st[i]]=1即可(st[i]表示第i个状态值分量)。
至此,我们顺利的解决了这4大隐式图搜索的问题,剩余的找最短路就是普通的BFS即可实现,从而完美的解决了本题。本题对于状态的定义是问题的关键。常量数组的设置也非常值得我们学习。
3.代码:
//#pragma comment(linker, "/STACK:1024000000,1024000000") #include<iostream> #include<algorithm> #include<cassert> #include<string> #include<sstream> #include<set> #include<bitset> #include<vector> #include<stack> #include<map> #include<queue> #include<deque> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> #include<ctime> #include<cctype> #include<functional> using namespace std; #define me(s) memset(s,0,sizeof(s)) typedef long long ll; typedef unsigned int uint; typedef unsigned long long ull; typedef pair <int, int> P; struct State { int st[11]; State(){} State(int a1,int a2,int a3,int a4,int a5,int a6,int a7,int a8,int a9) { st[1]=a1,st[2]=a2,st[3]=a3,st[4]=a4,st[5]=a5,st[6]=a6,st[7]=a7,st[8]=a8,st[9]=a9; } }; const int INF=100000000; const int up[]={0,0,0,0,1,2,3,4,5,6}; const int down[]={0,4,5,6,7,8,9,0,0,0}; const int lef[]={0,0,1,2,0,4,5,0,7,8}; const int rig[]={0,2,3,0,5,6,0,8,9,0}; const int tp[4][5]={{0,1,2,3,4},{0,4,1,2,3},{0,3,4,1,2},{0,2,3,4,1}}; queue<State>q; string s[10][10],t[10][10],p[10]; int st[11][11]; int vis[11]; int cor[11][11]; int d[1<<20]; int ans; void init_initial() { for(int i=0;i<3;i++) for(int j=1;j<=8;j++) cin>>s[i*3+1][j]>>s[i*3+2][j]>>s[i*3+3][j]; } void init_target() { for(int i=0;i<3;i++) for(int j=1;j<=8;j++) cin>>t[i*3+1][j]>>t[i*3+2][j]>>t[i*3+3][j]; } int Hash(State x)//对状态x进行压缩 { int res=0; for(int i=1;i<=9;i++) res=res+(x.st[i]<<(2*(i-1))); return res; } bool meshed(State x,int a,int e1,int b,int e2)//判断第a个方块的e1方位和第b个方块的e2方位是否都有齿轮 { int f1=st[a][tp[x.st[a]][e1]],f2=st[b][tp[x.st[b]][e2]]; //利用tp数组快速获得对应下标 return f1==1&&f2==1; //都是1,说明有齿轮耦合 } State rotate(State x,int y,int flag) { vis[y]=1; int l=lef[y],r=rig[y],u=up[y],d=down[y]; bool fl=0,fu=0,fd=0,fr=0; if(l&&!vis[l]&&meshed(x,l,3,y,1))fl=1; if(r&&!vis[r]&&meshed(x,r,1,y,3))fr=1; if(u&&!vis[u]&&meshed(x,u,4,y,2))fu=1; if(d&&!vis[d]&&meshed(x,d,2,y,4))fd=1; int z=x.st[y]; if(flag==1) z=(z==3)?0:z+1; //对第y块方块顺时针旋转90度 else z=(z==0)?3:z-1; x.st[y]=z; if(fl&&!vis[l])//如果第y块方块的左边发生齿轮耦合,且它没有旋转过,那么对左侧的方块进行旋转 { vis[l]=1; x=rotate(x,l,3-flag); } if(fr&&!vis[r]) { vis[r]=1; x=rotate(x,r,3-flag); } if(fu&&!vis[u]) { vis[u]=1; x=rotate(x,u,3-flag); } if(fd&&!vis[d]) { vis[d]=1; x=rotate(x,d,3-flag); } return x; } bool ok(State y)//利用cor数组实现对到达目标状态的判断 { for(int i=1;i<=9;i++) if(!cor[i][y.st[i]])return 0; return 1; } void bfs(State x) { while(!q.empty())q.pop(); q.push(x); while(!q.empty()) { x=q.front();q.pop(); int sx=Hash(x); for(int i=1;i<=9;i++) { me(vis); State y=rotate(x,i,1); int sy=Hash(y); if(d[sy]>d[sx]+1) { d[sy]=d[sx]+1; if(ok(y)) { ans=d[sy]; return; } q.push(y); } } } } bool same(int x) { for(int i=1;i<=8;i++) for(int j=0;j<8;j++) if(s[x][i][j]!=t[x][i][j])return 0; return 1; } void change(int x) //对第x个方块旋转90度(不考虑齿轮耦合) { for(int i=1;i<=8;i++)p[i]=""; for(int i=1;i<=8;i++) for(int j=8;j>=1;j--) p[i]=p[i]+s[x][j][i-1]; for(int i=1;i<=8;i++) s[x][i]=p[i]; } int main() { int T; scanf("%d",&T); while(T--) { init_initial(); //初始化初始状态 init_target(); //初始化目标状态 for(int i=1;i<=9;i++) for(int j=1;j<=4;j++) scanf("%d",&st[i][j]); //齿轮数组 fill(d,d+(1<<20),INF); //把所有的距离都设为INF d[0]=0,ans=-1; me(cor); for(int i=1;i<=9;i++) for(int j=0;j<4;j++) //对每个方块的第j次旋转是否等于目标状态进行预处理 { if(same(i))cor[i][j]=1; change(i); } bfs(State(0,0,0,0,0,0,0,0,0)); //利用BFS求最短路 printf("%d\n",ans); } }