魔板
https://loj.ac/problem/10027
题目描述
一块魔板由两行各四个数组成,初始状态为 :第一行:\(1,2,3,4\) ;第二行:\(5,6,7,8\)。并且魔板有\(3\)种操作,\(A\)为交换上下两行,\(B\)为将最右边一列转到最左侧,\(C\)为魔板中心顺时针旋转。给出目标状态,求出初始状态到目标状态的最少步数和操作序列。
思路
这道题显然需要搜索,而求最小步数肯定用\(bfs\)。但这道题最难的判重,如何将魔板的状态压缩在一个\(hash\)数组中进行判重,这里我们可以引入康托展开。
康托展开是针对于\(n\)的全排列,用于求全排列在所有顺序的排名。而魔板就可以看做一个\(8\)位的全排列。所以康托展开实质就是试图找有多少个全排列序列比已知序列要小。我们可以逐位分析,首先假设第一位为\(x\),那么第一位就比它小的有\((x - 1)* 7!\);同理,第二位为\(y\),那么第一位与它相同或第一位比它小的就需要考虑\(y\)和\(x\)的大小关系,因为我们未知比\(y\)小的是否已填过。这里其实与数位\(dp\)有异曲同工之妙,都是分析是否与界限前几位相等来考虑。所以我们只需寻找在第\(i\)位后比这一位值小的有几个即可。
有判重后正常打一遍\(bfs\)就完事了。
代码
#include <bits/stdc++.h>
using namespace std;
struct aa
{
int s[10][10];
}st,goal;
int jc[15]={1,1,2,6,24,120,720,5040},step[50000],pre[50000],ans[50000],p;
bool vis[50000];
queue<aa>q;
int turn(aa x) //康托展开
{
int t[10],ret=0;
for(int i=0;i<4;i++)t[i]=x.s[0][i];
for(int i=3;i>=0;i--)t[7-i]=x.s[1][i];
for(int i=0;i<8;i++)
{
int s=0;
for(int j=i+1;j<8;j++)
if(t[i]>t[j])s++;
ret+=s*jc[7-i];
}
return ret;
}
aa change(int way,aa x) //三种操作
{
aa ret;
if(way==1)
{
for(int i=0;i<4;i++)ret.s[1][i]=x.s[0][i];
for(int i=0;i<4;i++)ret.s[0][i]=x.s[1][i];
}
else if(way==2)
{
ret.s[0][0]=x.s[0][3];ret.s[1][0]=x.s[1][3];
for(int i=1;i<4;i++)
{
ret.s[0][i]=x.s[0][i-1];
ret.s[1][i]=x.s[1][i-1];
}
}
else if(way==3)
{
ret.s[0][0]=x.s[0][0];ret.s[1][0]=x.s[1][0];
ret.s[0][1]=x.s[1][1];ret.s[1][1]=x.s[1][2];
ret.s[0][2]=x.s[0][1];ret.s[1][2]=x.s[0][2];
ret.s[0][3]=x.s[0][3];ret.s[1][3]=x.s[1][3];
}
return ret;
}
void print(int k)
{
if(pre[k]==-1)return ;
print(pre[k]);
printf("%c",ans[k]-1+'A');
}
void bfs()
{
int l=turn(st);
q.push(st);step[l]=0;pre[l]=-1;
while(!q.empty())
{
aa now=q.front();q.pop();
int t=turn(now);
for(int i=1;i<=3;i++)
{
aa nex=change(i,now);
l=turn(nex);
if(!vis[l])
{
q.push(nex);
step[l]=step[t]+1;
ans[l]=i;
pre[l]=t;
vis[l]=1;
if(l==p){cout<<step[l]<<endl;print(l);return ;}
}
}
}
}
int main()
{
for(int i=0;i<4;i++)st.s[0][i]=i+1;
for(int i=3;i>=0;i--)st.s[1][i]=8-i;
int l=turn(st);vis[l]=1;
for(int i=0;i<4;i++)
scanf("%d",&goal.s[0][i]);
for(int i=3;i>=0;i--)
scanf("%d",&goal.s[1][i]);
p=turn(goal);
if(l==p)
printf("0");
else
bfs();
return 0;
}