经典八皇后问题:
#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;
struct node{
int tab[3][3];
int r,c;
int hash_val; //当前排列在全排列时候的大小位置
node* pre;
int op;
int f,g; //f:估计值 g:从起始点到当前点的花费
bool operator < (const node& rhs)const{
return f > rhs.f;
}
}st,ed;
node t[370000];
int tot;
int hash_tab[370000];
priority_queue<node> open;
int fn[10];
int ed_map[10][2];
int dir_i[4] = {0,0,1,-1};
int dir_j[4] = {1,-1,0,0};
char print_op[4] = {'r','l','d','u'};
//处理输入的字符
void In(char s[],node& t){
char ch;
int next = 0;
for(int i = 0;i < 3;++i){
for(int j = 0;j < 3;++j){
while(s[next] == ' ') ++next;
ch = s[next++];
if(isdigit(ch))
st.tab[i][j] = ch - '0';
else { // x 的位置
st.r = i;
st.c = j;
st.tab[i][j] = 0;
}
}
}
}
//获取估价函数值
int get_f(node a,int g){
int h = 0;
for(int i = 0;i < 3;++i)
for(int j = 0;j < 3;++j)
if(a.tab[i][j]) //距离目标点的曼哈顿距离
h += abs(i - ed_map[a.tab[i][j]][0]) + abs(j - ed_map[a.tab[i][j]][1]);
return g + h;
}
//康拓展开,判断该组合在所有排列中的位置
int get_hash(node a){
int ret;
ret = 0;
int num = 8;
for(int i = 0;i < 3;++i){
for(int j = 0;j < 3;++j){
int x = 0;
//当前这个数后有多少个比他小
for(int jj = j + 1;jj < 3;++jj)
if(a.tab[i][jj] < a.tab[i][j]) x++;
for(int ii = i + 1;ii < 3;++ii)
for(int jj = 0;jj < 3;++jj)
if(a.tab[ii][jj] < a.tab[i][j]) x++;
ret += fn[num] * x; //康拓展开
num--;
}
}
return ret;
}
//预处理
void init(){
memset(hash_tab,0,sizeof(hash_tab));
while(!open.empty()) open.pop();
tot = 0;
st.f = get_f(st,0); //获取估价函数值
st.g = 0;
st.hash_val = get_hash(st); //获取该组合在全排列的位置
open.push(st);
hash_tab[st.hash_val] = 1; //当前的组合排列已经遍历过了
}
void pre(){
//预处理阶乘
fn[0] = 1;
for(int i = 1;i < 9;++i) fn[i] = i * fn[i - 1];
//预处理结果
for(int i = 0;i < 3;++i)
for(int j = 0;j < 3;++j)
ed.tab[i][j] = (i * 3) + j + 1;
ed.tab[2][2] = 0;
ed.hash_val = get_hash(ed);
//预处理结果位置的映射
for(int i = 0;i < 3;++i)
for(int j = 0;j < 3;++j)
if(ed.tab[i][j]){
ed_map[ed.tab[i][j]][0] = i;
ed_map[ed.tab[i][j]][1] = j;
}
}
//判断逆序数的奇偶性
int get_preval(node a){
int ret = 0;
for(int i = 0;i < 3;++i)
for(int j = 0;j < 3;++j){
if(!a.tab[i][j]) continue;
int x = 0;
for(int jj = j + 1;jj < 3;++jj)
if(a.tab[i][jj] && a.tab[i][jj] < a.tab[i][j]) x++;
for(int ii = i + 1;ii < 3;++ii)
for(int jj = 0;jj < 3;++jj)
if(a.tab[ii][jj] && a.tab[ii][jj] < a.tab[i][j]) x++;
ret += x;
}
return ret & 1;
}
bool pre_solve(){
return(get_preval(st)^(get_preval(ed)));
}
void change(node &tmp,node a,int nextr,int nextc,int _i,int idx){
for(int i = 0;i < 3;++i)
for(int j = 0;j < 3;++j)
tmp.tab[i][j] = a.tab[i][j];
swap(tmp.tab[nextr][nextc],tmp.tab[a.r][a.c]);
tmp.hash_val = get_hash(tmp);
tmp.r = nextr;
tmp.c = nextc;
tmp.pre = &t[idx];
tmp.op = _i; //移动的方向
tmp.g = a.g + 1; //当前的步数
tmp.f = get_f(tmp,tmp.g);
}
bool check(int i,int j){
if(i > 2 || i < 0 || j > 2 || j < 0) return false;
return true;
}
//打印路径
void path(node* a){
if(a->hash_val == st.hash_val) return ;
path(a->pre);
printf("%c",print_op[a->op]);
}
//A*
void Astar(){
int nextr,nextc;
node ans = st;
int fla = 0;
if(st.hash_val != ed.hash_val)
while(!open.empty()){
node a = open.top();
open.pop();
t[++tot] = a;
for(int i = 0;i < 4;++i){
nextr = a.r + dir_i[i];
nextc = a.c + dir_j[i];
if(check(nextr,nextc)){
node tmp;
change(tmp,a,nextr,nextc,i,tot);
if(hash_tab[tmp.hash_val]) continue;
if(tmp.hash_val == ed.hash_val){
fla = 1;
ans = tmp;
break;
}
open.push(tmp);
}
}
if(fla) break;
hash_tab[a.hash_val] = 1;
}
path(&ans);
puts("");
}
int main()
{
// freopen("Input.txt","r",stdin);
char ss[12];
while(gets(ss)){
pre();
In(ss,st);
init();
if(pre_solve())
puts("unsolvable");
else
Astar();
}
return 0;
}
对于空格(0)的左移/右移操作,对应序列不变(逆序数也就不变)
对于空格(0)的上移/下移操作,相当于序列的某个数字前移/后移两位,该序列的逆序数奇偶性不变。
所以求初始状态与目标状态的逆序数可作出判断
例中前者为奇,后者为偶,因此无解
利用奇偶性判断所给出的初始状态有无解.
判别方法是:
以数组为一维的举例子.
将八数码的一个结点表示成一个数组a[9],空格用0表示,设临时函数p(x)定义为:x数所在位置前面的数比x小的数的个数,
其中0空格不算在之内,那设目标状态为b[9],那r=sigma(p(x)) sigma()表示取所有的x:1-8并求和,
那对于初始状态a[9],t=sigma(p(x)),如果r和t同为奇数或者同为偶数,那么该状态有解,否则无解。
考虑到四种移动方法对sigma(p(x))的影响,左移和右移是不会影响它的值的,
更不会影响奇偶性,如果是上移或者下移就会影响:
上移:一次上移会使一个元素向前跳两个数字的位置,设这两个数字为a1,a2,
不妨设a1<a2,移的这个数字设为a0,那无非只有以下三次情况:
1,a0<a1<a2,考虑它们三者的p(x)值,p(a0)不变,p(a1)++,p(a2)++,总体增加了2
2,a1<a0<a2,p(a0)--,p(a1)不变,p(a2)++,总体不变
3,a1<a2<a0,p(a0)-=2,p(a1),p(a2)不变,总体减小了2
综合起来的结论就是不会影响sigma(p(x))的奇偶性。