题目链接:Eight II HDU - 3567
===================================================
Eight II
Time Limit: 2000 ms
Memory Limit: 65536 kB
Description
Eight-puzzle, which is also called “Nine grids”, comes from an old game.
In this game, you are given a 3 by 3 board and 8 tiles. The tiles are numbered from 1 to 8 and each covers a grid. As you see, there is a blank grid which can be represented as an ‘X’. Tiles in grids having a common edge with the blank grid can be moved into that blank grid. This operation leads to an exchange of ‘X’ with one tile.
We use the symbol ‘r’ to represent exchanging ‘X’ with the tile on its right side, and ‘l’ for the left side, ‘u’ for the one above it, ‘d’ for the one below it.
A state of the board can be represented by a string S using the rule showed below.
The problem is to operate an operation list of ‘r’, ‘u’, ‘l’, ‘d’ to turn the state of the board from state A to state B. You are required to find the result which meets the following constrains:
- It is of minimum length among all possible solutions.
- It is the lexicographically smallest one of all solutions of minimum length.
Input
The first line is T (T <= 200), which means the number of test cases of this problem.
The input of each test case consists of two lines with state A occupying the first line and state B on the second line.
It is guaranteed that there is an available solution from state A to B.
Output
For each test case two lines are expected.
The first line is in the format of “Case x: d”, in which x is the case number counted from one, d is the minimum length of operation list you need to turn A to B.
S is the operation list meeting the constraints and it should be showed on the second line.
Sample Input
2
12X453786
12345678X
564178X23
7568X4123
Sample Output
Case 1: 2
dd
Case 2: 8
urrulldr
===================================================
这里是要求是给出 起点和终点序列 然后得出最短操作 而且相同长度要求最小字典顺序(重点,我这里wrong了很多次 看题目才知道)
第一遍:就是A* + map 尝试 然后 timelimit。
第二遍:看其他人代码,有两种,一种就是设立limit的dfs;这几天了解一下 这是IDA算法。迭代加深的A算法。
f(x) = 层数 + 与目标序列的相同字符曼哈顿距离之和;然后从第一个输入的序列的f(x)为limit,然后limit逐步以最小量增加为dextd直到找到答案位置。
===================================================
知识补充:IDA*算法——这里我直接给出大牛代码的链接。https://www.cnblogs.com/DOLFAMINGO/p/7538577.html
===================================================
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
//int的取值范围为: -2^31——2^31-1,即-2147483648——2147483647
const int INF = 2e9;
int Y[10],M[10],op=1,nextd;
char C[1234];
int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1},
{6,-1,4,0},{7,3,5,1},{8,4,-1,2},
{-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}};
char d[5] = "dlru";
int forLimit(int *a){
int ans = 0;
for(int i=0;i<9;i++) if(a[i]!=0) ans += abs(i/3 - M[a[i]]/3) + abs(i%3 - M[a[i]]%3);
return ans;
}
bool IDAstar(int k,int step,int pre,int limit){
int h = forLimit(Y);
if(h==0){
printf("Case %d: %d\n",op++,step);
C[step] = '\0';puts(C);
return true;
}
if(h+step>limit){
nextd = min(nextd,h+step);
return false;
}
for(int i=0;i<4;i++){
int pos = changeId[k][i];
if(pos==-1||i+pre==3) continue;
C[step] = d[i];
swap(Y[k],Y[pos]);
if(IDAstar(pos,step+1,i,limit)) return true;
swap(Y[k],Y[pos]);
}
return false;
}
int main()
{
int _;scanf("%d",&_);
while(_--){
char a[10],b[10];
int k;
scanf("%s%s",a,b);
for(int i=0;i<9;i++)
if(a[i]=='X') Y[i] = 0,k = i;
else Y[i] = a[i] - '0';
for(int i=0;i<9;i++)
if(b[i]=='X') M[0] = i;
else M[b[i]-'0'] = i;
//for(int i=0;i<9;i++) cout<<Y[i];cout<<endl;
//for(int i=0;i<9;i++) cout<<M[i];cout<<endl;
//---------------------------
for(int limit = forLimit(Y);;limit = nextd){
nextd=INF;
if(IDAstar(k,0,INF,limit)) break;
}
}
return 0;
}
自己码了一遍,发现易错点在于
- nextd = INF 需要每次循环 重新定义为INF
- f(x) = h(x) + g(x).
这里构造启发函数,先试着用简单的不同字符数来运算,结果跑不出来,后改为曼哈顿距离,然后试着连着X字符也计算,发现wrong,这里这样的确不是最优,但是举不出反例,所以也不知道为什么?最后就用作者一样,不算X的曼哈顿距离,然后就通过了。 - 最后最重要还是减枝,避免死循环。
===================================================
另一种就是预处理 ,这里很巧妙 我想了一早上 想通了;重点 x 是操作单位不能动,其他字符是可替换的。
举例子:645X312 ---->456X123 的操作 和 123X456---->231X564的操作一样 为什么?因为这只是简单字符替换 ,这种替换并不影响结果。甚至645X312可以替换成qweXyui,不影响的。
然后就是12345678X,1234567X8…9种X在不同位置的预处理,然后通过给出起点X位置进行得出结果。
重点 要 {dlru}这样进行遍历 因为需要最小字典序列
===================================================
#include <iostream>
#include <queue>
#include <cstring>
#include <string>
using namespace std;
const int M = 400000;
int vis[9][M];
int pre[9][M];
//-----------------------------------------------------------
void show(int *a){
for(int i=0;i<9;i++) cout<<a[i];cout<<endl;
}
//------------------------------------------------------------
int changeId[9][4] = {{3,-1,1,-1},{4,0,2,-1},{5,1,-1,-1},
{6,-1,4,0},{7,3,5,1},{8,4,-1,2},
{-1,-1,7,3},{-1,6,8,4},{-1,7,-1,5}};
char d[5] = {'d','l','r','u'};
//康托展开======================================================
const int fk[10] = {1,1,2,6,24,120,720,5040,40320,362880};
int cantor(int *a){
int ans = 0;
for(int i=0;i<8;i++){
int small = 0;
for(int j=i+1;j<9;j++) if(a[j]<a[i]) small++;
ans += small*fk[8-i];
}
return ans;
}
//----------------------------------------------------------
struct node{
int num[9],zero,hashcode;
};
void bfs(int x){
node now;
for(int i=0,j=1;i<9;i++)
if(i==x) now.num[i] = 0;
else now.num[i] = j++;
now.zero=x;
now.hashcode = cantor(now.num);
queue<node> q;
q.push(now);
vis[x][now.hashcode] = 1;
while(!q.empty()){
node p = q.front();q.pop();
int zero = p.zero,hashcode = p.hashcode;
for(int i=0;i<4;i++){
if(changeId[zero][i] == -1) continue;
node temp = p;
temp.zero = changeId[zero][i];
swap(temp.num[zero],temp.num[temp.zero]);
temp.hashcode = cantor(temp.num);
if(vis[x][temp.hashcode]==-1){
vis[x][temp.hashcode] = i;
pre[x][temp.hashcode] = hashcode;
q.push(temp);
}
}
}
}
//预处理=================================================
void init(){
memset(vis,-1,sizeof vis);
memset(pre,-1,sizeof pre);
for(int i=0;i<9;i++) bfs(i);
}
//---------------------------------------------------------
int main(){
init();
int _,op=1;
scanf("%d",&_);
while(_--){
//----------------------
int k;
char a[10],b[10];
int c[10],u[10];
scanf("%s%s",a,b);
//printf("%s\n%s\n",a,b);
//进行符号兑换:c数组为兑换数组,得到兑换后的目标数组和X位置========
for(int i=0,j=1;i<9;i++)
if(a[i]=='X') k=i;
else c[a[i]-'0'] = j++;
for(int i=0;i<9;i++)
if(b[i]=='X') u[i] = 0;
else u[i] = c[b[i]-'0'];
//show(u);
//===============================
int can = cantor(u);
string ans = "";
while(can != -1){
ans = d[vis[k][can]] + ans;
can = pre[k][can];
}
printf("Case %d: %d\n",op++,ans.length()-1);
cout<<ans.substr(1)<<endl;
}
return 0;
}