1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 x
where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 5 6 7 8 5 6 7 8 5 6 7 8 5 6 7 8 9 x 10 12 9 10 x 12 9 10 11 12 9 10 11 12 13 14 11 15 13 14 11 15 13 14 x 15 13 14 15 x r-> d-> r->
The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.
Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.
1 2 3 x 4 6 7 5 8
is described by this list:
1 2 3 x 4 6 7 5 8
2 3 4 1 5 x 7 6 8Sample Output
ullddrurdllurdruldr
感觉POJ的测试数据有些水了,POJ上能过,HDU却超时了。
现在,我们对一个任意的棋局状态p=c[1]c[2]c[3]c[4]c[5]c[6]c[7]c[8]进行分析:
引理1:如果交换任何两个相邻的棋子,那么棋子数列的逆序数将发生奇偶性互变(奇偶性互变是指由奇数变为偶数,或由偶数变为奇数,下同)。
其证明很简单,假设交换的是c[i]和c[i+1],那么对于c[j](1≤j≤i-1或i+2≤j≤8)的逆序数并不改变。若交换之前 c[i]<c[i+1],那么交换之后,c[i]的逆序数不变,而c[i+1]的逆序数加1(因为c[i]成了它的一个逆序);同理,若交换之前 c[i]>c[i+1],那么交换之后,c[i]的逆序数减1,而c[i+1]的逆序数不变。所以,引理1成立。
引理2:如果棋子数列经过n次相邻棋子交换后,若n为偶数,则数列逆序数奇偶性不变;若n为奇数,则数列逆序数将发生奇偶性互变。
其证明可以由引理1简单推出。
引理3:在满足上述约定的八数码问题中,空格与相邻棋子的交换不会改变棋局中棋子数列的逆序数的奇偶性。
证明:显然空格与左右棋子交换不会改变棋子数列的逆序数(因为数列并没有改变)。现在考虑空格与上下棋子交换的情况:若空格与上方的棋子交换(假设交换是可行的),将得到一个新数列。若假设交换棋子为c[i]=X,那么原数列p=c[1]...X c[i+1]c[i+2]...c[8]将变为新数列q=c[1]...c[i+1]c[i+2]X ...c[8](注意:在棋盘中,上下相邻的两棋格之间隔有两个棋格)。由原数列p到新数列q的转变可以通过如下方式加以解释:用X与c[i+1]、 c[i+2]先后进行两次相邻交换而完成状态转变。所以根据引理2知,由p状态到q状态并不会改变改变棋子数列的逆序数的奇偶性。同理可证空格与下方棋子交换也不会改变棋子数列的逆序数的奇偶性。所以,空格与相邻棋子的交换不会改变棋局中棋子数列的逆序数的奇偶性。
定理1
(1)当初始状态棋局的棋子数列的逆序数是奇数时,八数码问题无解;
(2)当初始状态棋局的棋子数列的逆序数是偶数时,八数码问题有解。
证明:由引理3知,按照八数码问题的游戏规则,在游戏过程中,棋局的棋子数列的逆序数的奇偶性不会发生变化。而上面规定的目标状态没有逆序存在,所以目标状态下棋局的逆序数为偶数(实际为0)。显然,可能的初始状态棋局的棋子数列的逆序数可能为奇数,也可能为偶数(因为把一个初始状态中任意相邻两个棋子交换,得到的新的状态作为初始状态,它们的奇偶性相反)。所以,对于任意一个初始状态,若其棋局的棋子数列的逆序数为奇数,则永远也不可能达到目标状态,即八数码问题无解;若其棋局的棋子数列的逆序数为偶数,
通俗来说,我们可以将X看成空格·
1 2 3
4 5 6
7 8
将空格左右移动,显然对逆序数无影响。如果上下移动呢,比如空格和5交换,我们可以将其看作空格先和7 交换,再和6交换,进行了两次(由引理2可知对逆序数无影响,然后进行左右交换即可,其他情况同理。)所以交换位置对逆序数无影响。显然1 2 3 4 5 6 7 x逆序数为0。是偶数,如果给定的序列是奇数,显然是无结果的(HDU1043 要考虑这里,否则会超时,POJ1077就无所谓啦)。
这道题我是用A*+康托展开式(判重)来写的。
代码如下:
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<string.h>
#include<iostream>
#include<queue>
#include<map>
#include<string>
#include<algorithm>
#define inf 0x3f3f3f3f
using namespace std;
int dir[4][2]= {0,1,1,0,0,-1,-1,0}; //四个方向
char op[4] = {'r', 'd', 'l', 'u' }; //要与四个方向对应,方便记录路径
int vis[1000010]; //根据康托展开式最大的数来开数组,用于标记
int f[20] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320};
struct note
{
int state; //记录状态,康托展开式的结果,即此时数列是第几大。
int loc; //记录X位置,为了方便记录与操作(康托展开式),将X变为了9,无影响
int g; //当前步数
int h,f;// //h:下一步估算值,f=g+h:预估目标值(步数)
int s[30]; //用一维数组保存序列。
string dis; //用string类记录函数,这有一个好处,string类可以直接在尾部加字符。
friend bool operator<(note a,note b)
{
return a.f>b.f; //定义优先级,预估目标值小的先搜,节省时间。
}
};
int is_ok(note a,int t) ///计算逆序数,并判断奇偶性
{
int sum=0; /// poj1077 可以不用写这一步
for (int i=0 ; i<t ; i++) /// HDU 1043不写会超时
if (a.s[i]!=9)
for (int j=0 ; j<i ; j++)
if (a.s[j]!=9 && a.s[j]>a.s[i]) sum ++ ;
if (sum%2) return 0;
return 1;
}
int cantor(int s[]) //康托展开式,模板
{
int sum=0;
for(int i=0; i<9; i++)
{
int num=0;
for(int j=i+1; j<9; j++)
{
if(s[j]<s[i])num++;
}
sum+=num*f[8-i];
}
return sum+1;
}
int dis(int s[]) ///曼哈顿距离
{
int d=0;
for(int i=0; i<9; i++)
{
if(s[i]!=9)
{
int x=i/3,y=i%3; //目标数字状态(0~8)
int xx=(s[i]-1)/3,yy=(s[i]-1)%3;//此时数字状态(因为是1~9,所以要减1,对应)
d+=abs(xx-x)+abs(yy-y); //模板
}
}
return d;
}
int bfs(note p)
{
priority_queue<note>Q;
while(!Q.empty)Q.pop();
memset(vis,0,sizeof(vis));
note q;
p.state=cantor(p.s); //记录结果(一个序列对应唯一的结果)
p.h=dis(p.s); //计算距离
p.f =p.f+ p.h; //初始化
p.g=0;
vis[p.state]=1; //标记
p.dis.clear(); //清空·字符串
Q.push(p);
while(!Q.empty())
{
p=Q.top();
Q.pop();
if(p.state==1)
{
// printf("%d\n",p.g);
cout<<p.dis<<endl; //输出路径,不能直接用printf输出
return 1;
}
int x=p.loc/3; //化为二维坐标
int y=p.loc%3;
for(int i=0; i<4; i++)
{
int tx=x+dir[i][0];
int ty=y+dir[i][1];
if(tx<0||ty<0||tx>=3||ty>=3)continue;
q=p;
q.s[x*3+y]=q.s[tx*3+ty]; //交换
q.s[tx*3+ty]=9;
q.state=cantor(q.s); //再次更新状态
if(vis[q.state]==0)
{
vis[q.state]=1;
q.loc=tx*3+ty;
q.g++;
q.h=dis(q.s);
q.f=q.g+q.h;
q.dis+=op[i]; //在尾部·追加字符,记录路径。
Q.push(q);
}
}
}
return 0;
}
int main()
{
char str[50];
while(gets(str))
{
note a;
int l=strlen(str);
int t=0;
for(int i=0; i<l; i++)
{
if(str[i]==' ')continue;
else
{
if(str[i]=='x')a.s[t]=9,a.loc=t,t++;
else a.s[t++]=str[i]-'0';
}
}
if(is_ok(a,t)==0) //逆序数是奇数直接continue
{
printf("unsolvable\n");
continue;
}
int flag=bfs(a);
if(flag==0) printf("unsolvable\n");
}
return 0;
}