POJ 1077 Eight
题目链接:http://poj.org/problem?id=1077
有人说做搜索不做这道题,人生遗憾。我做完了之后,嗯,同意。
关于八数码问题,有以下几个问题需要想明白:
【状态表示和状态判断】
题目是按照连续9个字符的形式读入的,自然想到在node中用一个s[9]的数组存储当前状态。但是可以马上否认的是,我们不可能也用这个字符串来进行状态之间的比较,因为效率太低,操作麻烦,实现费劲。这个字符串一共有9!中排列方式,这里有一个定理:康托展开。很好地解决了存储和比较两个问题。通过康托展开,就可以把这9!个数字分别转换为一个int(注:9! = 362880 < 2^31-1)
【无解状态剪枝】
我们要的最后一个状态是{1, 2, 3, 4, 5, 6, 7, 8, x}, 这个排列的逆序数是0,是偶数。而对于任意一个排列,显然,因为x不表示数字,那么也就是说把x向左右两个方向移动并不会改变整个序列的逆序数,那么有结论:
如果初始序列的逆序数是一个奇数,则必然这个序列无解(必要,不充分)
有了以上两个想法,下一步就是选择搜索策略:
bfs可以,A*可以,IDA*也可以
【参考代码】
/**
* Name: Eight
* P_ID: POJ 1077
* Note: BFS + 康托展开
* Date: 2016-05-18
*
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 3;
const int cell = 362880;
int vis[cell];
int parent[cell];
char step[cell];
//cantor Base
const int fac[] = {
1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
//dir
const int dir[4][2] = {
{-1, 0}, {
1,0}, {
0, -1}, {
0, 1}}; //u, d, l, r
struct node {
char s[9];
int space;
};
//康拓定理正向加密过程
int Hash(const char* str)
{
int n = 9;
int num = 0;
int temp;
for(int i=0; i<n-1; ++i)
{
temp=0;
for(int j=i+1; j<n; j++)
{
if(str[j] < str[i])
temp++;
}
num += fac[str[i]-1] * temp;
}
return num;
}
//康托定理逆向解码过程
void get_node(int num, node &temp)
{
int n = 9;
int a[9];
for(int i=2; i<=n; ++i)
{
a[i-1] = num%i;
num /= i;
temp.s[i-1] = 0;
}
temp.s[0] = 0;
int rn, i;
for(int k=n; k>=2; --k)
{
rn = 0;
for(i=n-1; i>=0; --i)
{
if(temp.s[i]!=0)
continue;
if(rn==a[k-1])
break;
++rn;
}
temp.s[i] = k;
}
for(i=0; i<n; ++i)
{
if(temp.s[i]==0)
{
temp.s[i] = 1;
break;
}
}
temp.space = n - a[n-1] - 1;
}
//搜索
void bfs(const node& begin)
{
memset(vis, 0, sizeof(vis));
int u = Hash(begin.s);
vis[u] =