题目大意:
类似于华容道的按规则移动方块,达到预期状态。
算法思想及主要数据结构:
BFS(广搜)+ 康托展开 + Queue
1、BFS
I. 将初始状态放入队列,
II. 得到队列中的第一个状态,则从队列中pop
III. 得到pop出的状态与目标太进行比较,如果匹配则结束,否则对当前状态继续分别进行A、B、C三种操作,把得到的新状态依次放入队列。
IV.只要队列不为空,重复II操作。
2、康托展开
康托展开是一种特殊的哈希函数,它的使用范围是对于n个数的排列进行状态的压缩和存储,例如要对9的全排列进行判重.没有必要开一个10^9的数组,同时内存也不允许开到那么大的数组.对此,有人提出了优化,即对于一个n的排列数,没有必要开到10^n,因为在一个排列中每个数只出现一次,所以只要前n-1位确定了,前N位就确定了。
但是以上的想法仍不是可行的,因为N可以很大,例如15,所以便引入了康托展开:只需要确定这个排列在总的排列情况中是第几小的就可以了。
例如: {1,2,3,4,...,n}表示1,2,3,...,n的排列如 {1,2,3} 按从小到大排列一共6个。123 132 213 231 312321 。
代表的数字 1 2 3 4 5 6 也就是把10进制数与一个排列对应起来。
他们间的对应关系可由康托展开来找到。
如我想知道321是{1,2,3}中第几个小的数可以这样考虑 :
第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2*2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1*1!=1 所以小于321的{1,2,3}排列数有2*2!+1*1!=5个。所以321是第6个小的数。 2*2!+1*1!+0*0!就是康托展开。
再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个小数。
3、A、B、C操作的数学方法
A操作(结果存入m,n变量中)
after.x= curr.y;
after.y= curr.x;
B操作
after.x= after.x + curr.x[3]+curr.x[0]+curr.x[1]+curr.x[2];
after.y= after.y + curr.y[3]+curr.y[0]+curr.y[1]+curr.y[2];
C操作:
after.x = after.x +curr.x[0]+curr.y[1]+curr.x[1]+curr.x[3];
after.y = after.y +curr.y[0]+curr.y[2]+curr.x[2]+curr.y[3];
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
struct Record{
string x,y;
string op;
}start,target;
const int PermSize = 8;
int factory[] = { 1, 1, 2, 6, 24, 120,720, 5040 }; // 7的阶乘(n-1的阶乘)
bool isVisited[50000];
void initial(){
string tmp = "";
// 初始化初态魔板
start.x = "1234";
start.y = "8765";
start.op = "";
target.x = "";
target.y = "";
for(int i = 0;i < 4;i++){
cin >> tmp;
target.x += tmp;
}
for(int j = 0;j < 4;j++){
cin >> tmp;
target.y += tmp;
}
memset(isVisited, false ,sizeof(isVisited));
}
int cantor( string buf )
{
int i, j, counted;
int result = 0;
for ( i = 0; i < PermSize; ++i )
{
counted = 0;
for( j = i + 1; j < PermSize + 1; ++j )
if( buf[i] > buf[j] )
++counted;
result = result + counted * factory[PermSize - i - 1];
}
return result;
}
bool isEqual(const Record &A,const Record &B){
return ( A.x == B.x && A.y == B.y );
}
Record operate(Record curr, Record after,int operation)
{
switch(operation){
case 0:
after.x = curr.y;
after.y = curr.x;
after.op += "A";
break;
case 1:
after.x = after.x + curr.x[3]+curr.x[0]+curr.x[1]+curr.x[2];
after.y = after.y + curr.y[3]+curr.y[0]+curr.y[1]+curr.y[2];
after.op += "B";
break;
case 2:
after.x = after.x + curr.x[0]+curr.y[1]+curr.x[1]+curr.x[3];
after.y = after.y + curr.y[0]+curr.y[2]+curr.x[2]+curr.y[3];
after.op += "C";
break;
}
return after;
}
// 广搜
void bfs( int m ){
if( isEqual(start,target) ){
cout << 0 << endl;
return;
}
queue<Record> records;
records.push(start);
// 队列不为空
while( !records.empty() ){
Record curr = records.front();
records.pop();
// 如果操作次数大于要求次数则返回-1
if( curr.op.size() > m ) {
cout << -1 << endl;
return;
}
// 当前态与目标太匹配则返回
if( isEqual(curr, target) ){
cout << curr.op.size() << " " << curr.op << endl;
return;
}
// 循环进行A,B,C操作
for( int i = 0; i < 3; i++ ){
Record after;
after.x = "";
after.y ="";
after.op = curr.op;
after = operate(curr,after,i);
int cn = cantor( after.x + after.y );
if( !isVisited[ cn ]){
isVisited[ cn ] = true;
records.push(after);
}
}
}
}
int main(){
int m;
while( cin >> m && m != -1 ){
initial();
bfs(m);
}
return 0;
}