用《算法竞赛入门经典》里的两种判重映射都超时,最后网上搜才知道,数据严的时候应该用双向BFS
po下网上找的代码
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <algorithm>
using namespace std;
#define Maxn 400000
#define Maxm 400000
struct State
{
int a[9];
int zero;
int g;
State()
{
}
State(int b[9])
{
for(int i=0; i<9; i++)
{
a[i]=b[i];
if(b[i]==0) zero=i;
}
g=0;
}
};
int dist[2][Maxn];
int fact[9];
int vis[2][Maxm];
State first,last;
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
queue<State> q1,q2;
void init_lookup_table()
{
fact[0] = 1;
for(int i=1; i<9; i++) fact[i] = fact[i-1] * i;
}
bool same(State x,State y)
{
for(int i=0; i<9; i++)
{
if(x.a[i]!= y.a[i]) return false;
}
return true;
}
int getcode(State x)
{
int code=0;
for(int i=0; i<9; i++)
{
int cnt=0;
for(int j=i+1; j<9; j++)
{
if(x.a[i]>x.a[j]) cnt++;
}
code+=fact[8-i]*cnt;
}
return code;
}
int try_to_insert(State x,int kind)
{
int code = getcode(x);
if(vis[kind][code]) return 0;
dist[kind][code] = x.g;
return vis[kind][code] = 1;
}
int bfs()
{
int code;
int x,y,newx,newy,z,newz,d;
memset(dist,0,sizeof(dist));
memset(vis,0,sizeof(vis));
while(!q1.empty()) q1.pop();
while(!q2.empty()) q2.pop();
q1.push(first);
q2.push(last);
try_to_insert(first,0);
try_to_insert(last,1);
while(!q1.empty() || !q2.empty())
{
int n1 = q1.size();
int n2 = q2.size();
//处理环A
if(n1 < n2 || n2 == 0)
{
//一层扩展
for(int i=0; i<n1; i++)
{
State s = q1.front();
q1.pop();
code = getcode(s);
//在对方这个状态是否存在
if(vis[1][code]) return dist[0][code] + dist[1][code];
z = s.zero;
x = z/3;
y = z%3;
for(d=0; d<4; d++)
{
newx = x + dx[d];
newy = y + dy[d];
newz = newx*3 + newy;
if(newx>=0 && newx <3 && newy>=0 && newy<3)
{
State t = s;
t.a[newz] = s.a[z];
t.a[z] = s.a[newz];
t.g = s.g + 1;
t.zero = newz;
if(try_to_insert(t,0)) q1.push(t);
}
}
}
}
//处理环B
else
{
//一层扩展
for(int i=0; i<n2; i++)
{
State s = q2.front();
q2.pop();
code = getcode(s);
//在对方这个状态是否存在
if(vis[0][code]) return dist[0][code] + dist[1][code];
z = s.zero;
x = z/3;
y = z%3;
for(d=0; d<4; d++)
{
newx = x + dx[d];
newy = y + dy[d];
newz = newx*3 + newy;
if(newx>=0 && newx <3 && newy>=0 && newy<3)
{
State t = s;
t.a[newz] = s.a[z];
t.a[z] = s.a[newz];
t.g = s.g + 1;
t.zero = newz;
if(try_to_insert(t,1)) q2.push(t);
}
}
}
}
}
return 0;
}
//求逆序数对,要排除第二个数字是0的那种情况
int reversePair(State s)
{
int ans = 0;
for(int i=0; i<9; i++)
{
for(int j=i+1; j<9; j++) if(s.a[i]>s.a[j] && s.a[j]!=0) ans++;
}
return ans;
}
bool preJudge()
{
if(same(first,last))
{
puts("0");
return false;
}
else if((reversePair(first) + reversePair(last))%2)
{
puts("-1");
return false;
}
return true;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int t;
int ans;
int begin[9],end[9];
scanf(" %d",&t);
init_lookup_table();
while(t--)
{
for(int i=0; i<9; i++) scanf(" %d",&begin[i]);
State a(begin);
first = a;
for(int i=0; i<9; i++) scanf(" %d",&end[i]);
State b(end);
last = b;
if(preJudge())
{
ans = bfs();
printf("%d\n",ans);
}
}
return 0;
}
映射方法依然是书中的第一种,我不知道这叫什么,AC代码的博主叫它全排列映射。
其他都很好理解,唯独其中判断是否可达的条件即:
起点和终点的逆序奇偶性是否相同?不同则不可达。
也有人总结了简易版的证明且比较清晰:
为了方便讨论,我们把它写成一维 的形式,并以0代替空格 位置。那么表示如下:
1 2 3 4 5 6 7 8 0
通过实验得知,以下状态是无解的(交换了前两个数字1 2):
2 1 3 4 5 6 7 8 0
八数码问题的有解无解的结论:
一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和,也就是每个数字前面比它大的数字的个数的和,称为这个状态的逆序。
若两个状态的逆序奇偶性 相同,则可相互到达,否则不可相互到达。
由于原始状态的逆序为0(偶数),则逆序为偶数的状态有解。
也就是说,逆序的奇偶将所有的状态分为了两个等价类 ,同一个等价类中的状态都可相互到达。
简要说明一下:
当左右移动空格时,逆序不变。当上下移动空格时,相当于将一个数字向前(或向后)移动两格,跳过 的这两个数字要么都比它大(小),
逆序可能±2;要么一个较大一个较小,逆序不变
如 1 2 3 1 2 3 | | 1 2 3 1 2 3
4 5 6 -----> 4 5 0 前者逆序数0 后者为2 | | 4 7 6 -----> 4 7 0 前者为3 后者为3
7 8 0 7 8 6 | | 5 8 0 5 8 6