链接:http://acm.hdu.edu.cn/showproblem.php?pid=1430

题面:

在魔方风靡全球之后不久,Rubik先生发明了它的简化版——魔板。魔板由8个同样大小的方块组成,每个方块颜色均不相同,可用数字1-8分别表示。任一时刻魔板的状态可用方块的颜色序列表示:从魔板的左上角开始,按顺时针方向依次写下各方块的颜色代号,所得到的数字序列即可表示此时魔板的状态。例如,序列(1,2,3,4,5,6,7,8)表示魔板状态为:


1 2 3 4

8 7 6 5


对于魔板,可施加三种不同的操作,具体操作方法如下:


A: 上下两行互换,如上图可变换为状态87654321

B: 每行同时循环右移一格,如上图可变换为41236785

C: 中间4个方块顺时针旋转一格,如上图可变换为17245368


给你魔板的初始状态与目标状态,请给出由初态到目态变换数最少的变换步骤,若有多种变换方案则取字典序最小的那种。

题目分析:

看到最小状态转移步骤自然的想到bfs,所以需要对魔板的状态进行hash处理,这里状态数量有8!种,康托展开能够将每种排序都转换成一个唯一的序列号,而且全部状态的总序列号恰好在n!内分布。

以下贴网上大佬们的康托展开介绍:

HDU 1430(康拓展开hash)_数组

HDU 1430(康拓展开hash)_康托展开_02

所以运用康托展开+bfs就能搞定从12345678序列转移到任意序列所需的状态数最少。

但题目给的是一个初始状态和一个目标状态,所以我们需要建立hash对应数组m[],将初始状态对应成12345678序列,进行求解即可》》

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

string ans[45000];
int F[10], m[10];


void cantor(int s[], ll num, int k){//康托展开,把一个数字num展开成一个数组s,k是数组长度
bool h[10] = { 0 }; //0到k-1,表示是否出现过
for (int i = 0; i < k; i++){
int t = num / F[k - i - 1];
num %= F[k - i - 1];
for (int j = 0, pos = 0;; j++, pos++){
if (h[pos]) j--;
if (j == t){
h[pos] = true;
s[i] = pos + 1;
break;
}
}
}
}

void inv_cantor(int s[], ll &num, int k){//康托逆展开,把一个数组s换算成一个数字num
int cnt; num = 0;
for (int i = 0; i < k; i++){
cnt = 0;
for (int j = i + 1; j < k; j++)
if (s[i] > s[j]) cnt++;//判断几个数小于它
num += F[k - i - 1] * cnt;
}
}


inline void gao(int s[],char &ch){//这个搞事情函数容易写错。。我好菜啊,调了半天
switch(ch){
case 'A':for(int i=0;i<=3;i++)swap(s[i],s[7-i]);
break;
case 'B':for(int i=3;i>0;i--){swap(s[i],s[i-1]);swap(s[8-i],s[7-i]);}
break;
case 'C':swap(s[6],s[1]);swap(s[5],s[6]);swap(s[2],s[5]);
break;
}
}
void init(){
F[0] = 1; //预处理阶乘
for (int i = 1; i <= 8; i++)F[i] = F[i - 1] * i;

int s[8],t[8]; ll num,numt;
queue<ll>que; que.push(0ll);

while(que.size()){
num = que.front();que.pop();
cantor(t,num,8); //把id转换成排列数组t
for(char i='A';i<='C';i++){
memcpy(s,t,sizeof(s));
gao(s,i); //搞事情函数,对应ABC三种操作,由于题目要求字典序最小,所以bfs拓展的顺序也就是ABC
inv_cantor(s,numt,8);
if(ans[numt].size()==0)
ans[numt]=ans[num]+i,que.push(numt);
}
}
}
int main(){
init();
char n[10],t[10];ll num;int k[10];
while(cin>>n>>t){
if(strcmp(n,t)==0){//坑啊!如果没这个会输出AA,正确答案是输出空行
cout<<endl;
continue;
}
for(int i=0;i<8;i++)m[n[i]-'0']=i+1;
for(int i=0;i<8;i++)k[i]=m[t[i]-'0'];
inv_cantor(k,num,8);
cout<<ans[num]<<endl;
}
return 0;
}