8数码,又称九宫格,应该是大家都玩过的一种游戏。在一个3*3的棋盘上放有8个棋子,棋子可以上下左右移动,要求通过移动棋子,使棋盘从一种状态转换为另一种状态。
首先,明显地,这是一个搜索问题,共有9!种状态,并不算多,使用普通BFS或双向BFS就能解决,其次,为了保存状态,可以使用康托展开,这样能减省许多空间。
普通BFS在状态空间搜索时,搜索了许多无用的状态,导致了时间的浪费,因此,可以使用A*或IDA*。
使用A*的话,可以根据当前状态处于正确位置的棋子数目来建立启发函数,而IDA*,也就是迭代加深的A*算法,是指先指定搜索深度,然后用DFS进行搜索,若找不到目标状态的话,则增加搜索深度,直到找到目标状态为止。因此,IDA*也是深度受限的DFS,其效率要比A*还要高。
另外,初始状态能转变为目标状态的前提是,两者逆序数的奇偶性一致。可以简要的证明一下:
1:当x在某一行里移动时,不会改变该状态逆序数的奇偶性(很明显,因为序列的排列根本没变,x可是不计入排列的);
2:当x与另外一行的数字交换位置时,相当于该数字连续移动了两次,而这样同样不会影响逆序数的奇偶性。
代码如下:
A*
#include <cstdio>
#include <stack>
#include <set>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <map>
#include <iomanip>
#include <cmath>
#define LL long long
#define ULL unsigned long long
#define SZ(x) (int)x.size()
#define Lowbit(x) ((x) & (-x))
#define MP(a, b) make_pair(a, b)
#define MS(arr, num) memset(arr, num, sizeof(arr))
#define PB push_back
#define F first
#define S second
#define ROP freopen("input.txt", "r", stdin);
#define MID(a, b) (a + ((b - a) >> 1))
#define LC rt << 1, l, mid
#define RC rt << 1|1, mid + 1, r
#define LRT rt << 1
#define RRT rt << 1|1
#define BitCount(x) __builtin_popcount(x)
#define BitCountll(x) __builtin_popcountll(x)
#define LeftPos(x) 32 - __builtin_clz(x) - 1
#define LeftPosll(x) 64 - __builtin_clzll(x) - 1
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
using namespace std;
const double eps = 1e-8;
const int MAXN = 300 + 10;
const int MOD = 1000007;
const int M=20010;
const int N=500010;
const int d[][2] = { {0,1},{0,-1},{-1,0},{1,0} };
typedef pair<int, int> pii;
int f[9];
bool vis[N];
char dir[4]={ 'r','l','u','d' };
struct node
{
int state,step,pre; // 状态, 步骤,父亲结点
int f,h;
char c;
}dis[N];
struct cmp
{
bool operator()(node a,node b)
{
return a.f>b.f;
}
};
int getX(const char g[])
{
for (int i=0;i<9;i++) if (g[i]=='9') return i;
}
int getH(const char g[])
{
int sum=0;
for (int i=0;i<9;i++) {
int t=g[i]-'1';
sum+=abs(t/3-i/3)+abs(t%3-i%3); // 该位置的数字与应该在这个位置的数字的横纵坐标差值之和
}
return sum;
}
bool niu(char s[]) // 判断是否有解
{
int i,j,cnt=0;
for (i=0;i<9;i++)
{
if (s[i]=='9') continue;
for (j=i+1;j<9;j++) {
if (s[j]=='9') continue;
if (s[i]>s[j]) cnt++;
}
}
if (cnt&1) return false;
else return true;
}
int KT(const char s[]) // 康托展开
{
int i,j,sum=0;
for (i=0;i<9;i++) {
int t=0;
for (j=i+1;j<9;j++) if (s[i]>s[j]) t++;
sum+=t*f[8-i];
}
return sum;
}
void invKT(char s[],int state) // 康托逆展开
{
int i,j;
bool a[10];
MS(a,false);
//state--;
for (i=0;i<9;i++) {
int t=state/f[8-i] ;
for (j=0;j<9;j++) if (!a[j]) {
if (!t) break;
t--;
}
s[i]=j+'1';
a[j]=true;
state%=f[8-i];
}
s[i]='\0';
}
bool astar(int state)
{
int i,j;
char s[10];
node t;
t.state=state;
t.step=0;
t.pre=-1;
dis[state]=t;
priority_queue<node,vector<node>,cmp> q;
q.push(t);
MS(vis,false);
vis[state]=true;
while(!q.empty()){
t=q.top(); q.pop();
if (!t.state) return true;
invKT(s,t.state);
int pos=getX(s);
int x=pos/3,y=pos%3;
for (i=0;i<4;i++){
int xx=x+d[i][0];
int yy=y+d[i][1];
if (xx>=0 && xx<3 && yy>=0 && yy<3) {
swap(s[x*3+y],s[xx*3+yy]);
int temp=KT(s);
if (!vis[temp]) {
node t2;
t2.state=temp; // 结点自身的状态
t2.step=t.step+1;
t2.h=getH(s);
t2.f=t2.h+t2.step;
t2.pre=t.state; // 父亲结点的状态
t2.c=dir[i]; // 方向
dis[temp]=t2;
q.push(t2);
vis[temp]=true;
}
swap(s[x*3+y],s[xx*3+yy]);
}
}
}
return false;
}
void print(int state)
{
if (dis[state].pre==-1) return;
print(dis[state].pre);
printf("%c",dis[state].c);
}
int main()
{
int i,j;
char ch,s[10];
f[0]=f[1]=1;
for (i=2;i<=9;i++) f[i]=f[i-1]*i;
while(cin>>ch)
{
if (ch=='x') s[0]='9';
else s[0]=ch;
for (i=1;i<9;){
scanf("%c",&ch);
if (isdigit(ch)) s[i++]=ch;
else if (ch=='x') { s[i++]='9'; }
}
s[i]='\0';
int t=KT(s);
if (niu(s) && astar(t)) {
print(0); cout<<endl;
}
else puts("unsolvable");
}
}
IDA*
#include <cstdio>
#include <stack>
#include <set>
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <functional>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <string>
#include <map>
#include <iomanip>
#include <cmath>
#define LL long long
#define ULL unsigned long long
#define SZ(x) (int)x.size()
#define Lowbit(x) ((x) & (-x))
#define MP(a, b) make_pair(a, b)
#define MS(arr, num) memset(arr, num, sizeof(arr))
#define PB push_back
#define F first
#define S second
#define ROP freopen("input.txt", "r", stdin);
#define MID(a, b) (a + ((b - a) >> 1))
#define LC rt << 1, l, mid
#define RC rt << 1|1, mid + 1, r
#define LRT rt << 1
#define RRT rt << 1|1
#define BitCount(x) __builtin_popcount(x)
#define BitCountll(x) __builtin_popcountll(x)
#define LeftPos(x) 32 - __builtin_clz(x) - 1
#define LeftPosll(x) 64 - __builtin_clzll(x) - 1
const double PI = acos(-1.0);
const int INF = 0x3f3f3f3f;
using namespace std;
const double eps = 1e-8;
const int MAXN = 300 + 10;
const int MOD = 1000007;
const int M=20010;
const int N=500010;
const int d[][2] = { {0,1},{0,-1},{-1,0},{1,0} };
typedef pair<int, int> pii;
int f[9],path[N];
bool vis[N];
int pathLen,limit;
int next[9][4]= // 行表示x在九宫格的位置,4列表示4个方向,表示移动后的位置,-1表示越界
{
{-1,3,1,-1},
{0,4,2,-1},
{1,5,-1,-1},
{-1,6,4,0},
{3,7,5,1},
{4,8,-1,2},
{-1,-1,7,3},
{6,-1,8,4},
{7,-1,-1,5}
};
inline int getX(const char g[])
{
for (int i=0;i<9;i++) if (g[i]=='9') return i;
}
int getH(const char g[])
{
int sum=0;
for (int i=0;i<9;i++) {
int t=g[i]-'1';
sum+=abs(t/3-i/3)+abs(t%3-i%3); // 该位置的数字与应该在这个位置的数字的横纵坐标差值
}
return sum;
}
int KT(const char s[])
{
int i,j,sum=0;
for (i=0;i<9;i++) {
int t=0;
for (j=i+1;j<9;j++) if (s[i]>s[j]) t++;
sum+=t*f[8-i];
}
return sum;
}
int niu(const char s[])
{
int i,j,cnt=0;
for (i=0;i<9;i++)
{
if (s[i]=='9') continue;
for (j=i+1;j<9;j++) {
if (s[j]=='9') continue;
if (s[i]>s[j]) cnt++;
}
}
return cnt;
}
bool ID_Astar(char s[],int len,int x)
{
int i,j;
int state=KT(s);
if (len<=limit){
if (state==0) {
pathLen=len;
return true;
}
}else return false; // over the limit
for (i=0;i<4;i++){
if (next[x][i]==-1) continue; // 不能向该方向移动
if (len>0 && abs(i-path[len-1])==2) continue; // 不向上一次的相反方向移动
swap(s[x],s[next[x][i]]);
int t=getH(s);
path[len]=i;
if (t+len<=limit && ID_Astar(s,len+1,next[x][i])) return true;
swap(s[x],s[next[x][i]]);
}
return false;
}
void print()
{
for (int i=0;i<pathLen;i++){
switch(path[i])
{
case 0:
printf("l"); break;
case 1:
printf("d"); break;
case 2:
printf("r"); break;
case 3:
printf("u"); break;
}
}
}
int main()
{
int i,j;
char ch,s[10];
f[0]=f[1]=1;
for (i=2;i<=9;i++) f[i]=f[i-1]*i;
while(cin>>ch)
{
if (ch=='x') s[0]='9';
else s[0]=ch;
for (i=1;i<9;){
scanf("%c",&ch);
if (isdigit(ch)) s[i++]=ch;
else if (ch=='x') { s[i++]='9'; }
}
s[i]='\0';
int t=getX(s);
limit=getH(s);
if ((niu(s)&1)==0) {
while (!ID_Astar(s,0,t)) limit++;
print(); cout<<endl;
//puts("Y");
}
else puts("unsolvable");
}
}