题目来源:SJTU 4013
Description
描述
在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
“A”:交换上下两行;
“B”:将最右边的一列插入最左边;
“C”:魔板中央四格作顺时针旋转。
下面是对基本状态进行操作的示范:
A: 8 7 6 5
1 2 3 4
B: 4 1 2 3
5 8 7 6
C: 1 7 2 4
8 6 3 5
对于每种可能的状态,这三种基本操作都可以使用。 你要编程计算用最少的基本操作完成基本状态到目标状态的转换,输出基本操作序列。
Input Format
只有一行,包括8个整数,用空格分开(这些整数在范围 1——8 之间)不换行,表示目标状态。
Output Format
第一行: 包括一个整数,表示最短操作序列的长度。 第二行: 在字典序中最早出现的操作序列,用字符串表示,除最后一行外,每行输出60个字符。
Sample Input
2 6 8 4 5 7 3 1
Sample Output
7
BCABCCB
Limits
时间限制:1000ms
=======================我是华丽的分割线=======================
注意要点
1、数据输入是顺时针的,正确把数据放入数组后,后续的三种操作都可以正常进行
2、题目要求的是从基本状态走向目标状态,而非还原操作
我的分析
1、需要构建一个类保存信息,包含2个数据成员:走到这一步最短的步骤,该数组的康托展开
2、需要康托函数和逆康托函数,将数组整合为一个数表示当前状态,配合散列表快速查找该状态是否已经抵达过
3、ABC三种操作分别封装为函数,读入一个康托数,解析成数组进行变换,然后封装为康托数return
4、利用队列进行广度优先搜索
#include <iostream>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
int cantor(int arr[]); // 康托函数
void uncantor(int x,int arr[]); // 逆康托函数
// 三种操作函数,读入一个康托数
int A(int a);
int B(int a);
int C(int a);
// 存放阶乘
int fac[9];
struct moban
{
string s; // 记录步骤
int c; // 康托数
moban(string a="", int b=-1):s(a), c(b) {} // 康托数从0开始,用-1避免冲突
};
int main()
{
queue <moban> q; // 广度优先搜索队列
int i, j, k, t[8], r=0, zhun[]={1, 2, 3, 4, 8, 7, 6 ,5}, biao;
// i, j, k 循环变量 r 步数
// t[8]记录输入的数据
// zhun[]开始的基本状态 biao 存储基本状态对应的康托数
bool flag = true, hash[50000] = {false};
// 计算阶乘,为康托函数服务,背得出的也可以自己直接写
fac[0] = 1;
for (i=1; i<=8; ++i)
fac[i] = fac[i-1] * i;
// 读入数据,注意顺时针
for (i=0; i<4; ++i)
cin >> t[i];
for (i=7; i>3; --i)
cin >> t[i];
// 计算目标康托数
biao = cantor(t);
// 将基本状态入队
moban a("", cantor(zhun));
q.push(a);
while (flag) // 开始搜索
{
r++;
int m = q.size();
for (i=0; i<m && flag; ++i)
{
moban tmp = q.front();
q.pop();
if (tmp.c == biao)
{
flag = false;
string res = tmp.s;
int le = res.size(), hang = le/60; // 计算输出行数
cout << r-1 << endl;
for (k=0; k<hang+1; ++k) // 每行60个字符
{
for (j=0; j<60 && le>0; ++j)
{
le--;
cout << res[60*hang+j];
}
cout << endl;
}
}
if (flag)
{
int a1=A(tmp.c), a2=B(tmp.c), a3=C(tmp.c);
// 判断三种操作后产生的情况是否出现过,没有则入队,并记录相应的操作步骤
if (!hash[a1])
{
hash[a1] = true;
string f=tmp.s + "A";
moban x(f, a1);
q.push(x);
}
if (!hash[a2])
{
hash[a2] = true;
string f=tmp.s + "B";
moban x(f, a2);
q.push(x);
}
if (!hash[a3])
{
hash[a3] = true;
string f=tmp.s + "C";
moban x(f, a3);
q.push(x);
}
}
}
}
system("pause");
return 0;
}
int cantor(int arr[])
{
int i, j, t, ans=0;
for (i=0; i<7; ++i)
{
t=0;
for (j=i+1; j<=7; ++j)
if (arr[j]<arr[i]) t++; // 计算逆序数
ans = (ans+t)*(7-i); // 阶乘求和简化运算,不理解的话自己拿草稿纸
}
return ans;
}
void uncantor(int x,int arr[])
{
int i,j,l,t,u[8];
memset(&u,0,sizeof(u));
for (i=0; i<=7; ++i)
{
t = x/fac[7-i];
x -= t*fac[7-i];
for (j=0,l=0; l<=t; ++j)
if (u[j]==0) l++;
j--;
u[j] = 1;
arr[i] = j + 1;
}
}
// 由于数据不多,直接数组定位转换
int A(int a)
{
int tmp[8], s[8];
uncantor(a, tmp);
s[0] = tmp[4];
s[1] = tmp[5];
s[2] = tmp[6];
s[3] = tmp[7];
s[4] = tmp[0];
s[5] = tmp[1];
s[6] = tmp[2];
s[7] = tmp[3];
return cantor(s);
}
int B(int a)
{
int tmp[8], s[8];
uncantor(a, tmp);
s[0] = tmp[3];
s[1] = tmp[0];
s[2] = tmp[1];
s[3] = tmp[2];
s[4] = tmp[7];
s[5] = tmp[4];
s[6] = tmp[5];
s[7] = tmp[6];
return cantor(s);
}
int C(int a)
{
int tmp[8], s[8];
uncantor(a, tmp);
s[0] = tmp[0];
s[1] = tmp[5];
s[2] = tmp[1];
s[3] = tmp[3];
s[4] = tmp[4];
s[5] = tmp[6];
s[6] = tmp[2];
s[7] = tmp[7];
return cantor(s);
}