目录
题目:洛谷P1379八数码难题
题目描述
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
思路
Step1:哈希每个排列组合数
因为只有0-8共9个数字,所有可能的状态数等于9个数的排列数,即362880个。因为数不多,所以我们可以把每个排列所得的数装到一个int里,分配给每个组合一个id,也就是哈希一下。在求全排列我们可以通过dfs,也可以通过STL的next_permutation()函数。
代码:分配id
const int N=400000;
bool vis[10];
int num[N];
int id;
void dfs(int x, int sum) {
if ( x==9 ) {
id++;
num[id]=sum;
return;
}
for(int i=0; i<9; i++) {
if (vis[i]) continue;
vis[i]=1;
dfs(x+1,sum*10+i);
vis[i]=0;
}
}
因为排列数是递增的,所以每次在求该组合的 id 时可以用二分法快速取得。
代码:查询每个组合数对应的id
int get_id(int n) {
int l=1, r=362880;
while(l<=r) {
int mid=l+(r-l)/2;
if ( n==num[mid] ) return mid;
else if ( n<num[mid] ) r=mid-1;
else l=mid+1;
}
}
Step2:考虑如何把这个组合数转换成3x3的模式以及从3x3模式转换为组合数模式
在求出该状态的3x3地图模式时,顺便可以把0的位置求出来。
注意:因为是从末开始取的,所以在建立3x3图的形式的时候,要逆着建
代码:转换成3x3模式并求出0的坐标
int mp[5][5]
int x_0,y_0;
void get_0(int n) {
for(int i=3; i>=1; i--) {
for(int j=3; j>=1; j--) {
mp[i][j]=n%10;
if ( mp[i][j]==0 ) x_0=i, y_0=j;
n/=10;
}
}
}
代码:从3x3模式转换为组合数模式
int get_num() {
int sum=0;
for(int i=1; i<=3; i++) {
for(int j=1; j<=3; j++) {
sum=sum*10+mp[i][j];
}
}
return sum;
}
Step3:用双向BFS求得最少步骤
每次得到这个组合数时,枚举0得四个方向,然后记录交换后的状态。
因为起点和终点都是明确的,所以可以直接双向BFS加速!
我们实现这一部分功能之前先思考需要什么:用 f [ ] 数组来记录路径,用到 get_0() 函数得到图并得到0的位置,枚举四个方向 dir[4][2] 数组,每次还会用到 get_id() 函数来得到 id,一个check()函数来检验0的交换是否越界。剩余一些小细节会在代码中注释
注意:每次交换0的位置以后还需交换回来
代码:求得最少步骤
int f[N];
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
bool check(int x, int y) {
return x>=1 && x<=3 && y>=1 && y<=3;
}
int bfs(int p1, int p2) {
queue<int> q;
f[p2]=-1, f[p1]=1; //双向dfs起点和终点标记。
q.push(p1); q.push(p2);
while(!q.empty()) {
int u=q.front(); q.pop();
get_0(num[u]); //得到当前排列的3x3模式以及0的位置
for(int i=0; i<4; i++) { //枚举四个交换方向
int nx=x_0+dir[i][0], ny=y_0+dir[i][1];
if ( check(nx,ny) ) {
swap(mp[x_0][y_0],mp[nx][ny]); //进行交换
int p=get_id(get_num()); //得到交换后的id
if ( !f[p] ) { //如果这个状态没有到达过就进行更新
//双向BFS,如果从起点过来就是正数,终点过来就是负数
//用这个Abs函数巧妙解决问题
f[p]=f[u]+f[u]/abs(f[u]);
q.push(p);
}
//如果乘积为负数,那么两点相交,找到最少步骤
else if ( f[p]*f[u]<0 ) {
return abs(f[p]-f[u])-1;
}
//注意每次交换以后要交换回来!
swap(mp[x_0][y_0],mp[nx][ny]);
}
}
}
return -1; //返回-1没找到(debug的时候用)
}
完整代码
//洛谷P1379-八数码难题
#include<bits/stdc++.h>
using namespace std;
const int N=400000;
const int Ans=123804765;
const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
bool vis[10];
int f[N];
int num[N];
int id=0;
int mp[5][5];
int x_0,y_0;
void dfs(int x, int sum) {
if ( x==9 ) {
id++;
num[id]=sum;
return;
}
for(int i=0; i<9; i++) {
if (vis[i]) continue;
vis[i]=1;
dfs(x+1,sum*10+i);
vis[i]=0;
}
}
int get_id(int n) {
int l=1, r=362880;
while(l<=r) {
int mid=l+(r-l)/2;
if ( n==num[mid] ) return mid;
else if ( n<num[mid] ) r=mid-1;
else l=mid+1;
}
}
void get_0(int n) {
for(int i=3; i>=1; i--) {
for(int j=3; j>=1; j--) {
mp[i][j]=n%10;
if ( mp[i][j]==0 ) x_0=i, y_0=j;
n/=10;
}
}
}
bool check(int x, int y) {
return x>=1 && x<=3 && y>=1 && y<=3;
}
int get_num() {
int sum=0;
for(int i=1; i<=3; i++) {
for(int j=1; j<=3; j++) {
sum=sum*10+mp[i][j];
}
}
return sum;
}
int Abs(int x) {
return x>0?x:-x;
}
int bfs(int p1, int p2) {
queue<int> q;
f[p2]=-1, f[p1]=1;
q.push(p1); q.push(p2);
while(!q.empty()) {
int u=q.front(); q.pop();
get_0(num[u]);
for(int i=0; i<4; i++) {
int nx=x_0+dir[i][0], ny=y_0+dir[i][1];
if ( check(nx,ny) ) {
swap(mp[x_0][y_0],mp[nx][ny]);
int p=get_id(get_num());
if ( !f[p] ) {
f[p]=f[u]+f[u]/Abs(f[u]);
q.push(p);
}
else if ( f[p]*f[u]<0 ) {
return Abs(f[p]-f[u])-1;
}
swap(mp[x_0][y_0],mp[nx][ny]);
}
}
}
return -1;
}
void solve() {
dfs(0,0);
int n;
scanf("%d",&n);
int p1=get_id(n), p2=get_id(Ans);
if ( p1==p2 ) {
printf("0\n");
return;
}
printf("%d\n",bfs(p1,p2));
}
int main() {
// freopen("in.txt","r",stdin);
solve();
return 0;
}