P1379 八数码难题

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用0表示

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)


除了搜索还真的没有方法下手了。我们可以搜索空格如何走,那么状态就为:

当前的棋盘,空格的位置和走过的步数。棋盘可以用全局变量来存。每次往上下左右四个方向搜即可。

考虑剪枝

1.上下界剪枝:

上界:不好求

下界:最好的情况就是——当前情况有多少个位子与目标状态不同,即

\[\sum_{i=1}^{3}\sum_{j=1}^{3}[g[i][j]≠des[i][j]] \]

虽然下界对剪枝没什么用

2.搜索顺序优化:由于一个地方可以重复走所以顺序随意。

3.排除等效冗余:往前走一步然后往回走一步没有意义,所以我们应该避免往上一次的反方向走。

4.最优化剪枝:设当前的答案为ans,那么当深度>ans时就没必要搜了。

5.记忆化:日常没什么好记的

并没有剪掉多少诶......考虑到答案很小(观察法),我们可以用迭代加深来优化DFS。

从1开始枚举深度,由迭代加深性质可知当第一次搜索到答案时必定是最优解,所以我们没必要做最优化剪枝了。

显然加了迭代加深之后固然能够起作用,但是我们又扔掉了一个好的剪枝——最优化剪枝,实际上效果不明显。并且剪枝已经剪不动了,当搜索到答案的深度时,我们必然会面对一个庞大的搜索树。

由于IDFS搜到的第一个答案就是最优解,所以搜到后可以直接跳出搜索。我们只需要让程序更快地搜到最优解即可。这时就加个启发式IDA * 吧~

设计估价函数。结合上面提到的下界,我们可以设计这样一个函数:

\[f(g[][])=\sum_{i=1}^{3}\sum_{j=1}^{3}[g[i][j]≠des[i][j]] \]

当当前深度加上预估值>当前的最大深度时就不需要往下搜了。

#include<iostream>
#include<cstring>
#include<cstdio>
#define maxn 4
using namespace std;

const int des[4][4]={{},{0,1,2,3},{0,8,0,4},{0,7,6,5}};
const int dir[4][2]={{-1,0},{0,-1},{0,1},{1,0}};
int g[maxn][maxn],ans;

inline int Abs(const int &x){ return x<0?-x:x; }
inline int evaluate(){
    int cnt=0;
    for(register int i=1;i<=3;i++) for(register int j=1;j<=3;j++) if(g[i][j]!=des[i][j]) cnt++;
    return cnt;
}
inline bool check(const int &x,const int &y){ return x<1||3<x||y<1||3<y; }
void IDAstar(int x,int y,int dep,int pre_dir,const int &maxdep){
    if(dep==maxdep){ if(!evaluate()) ans=true; return; }
    for(register int i=0;i<4;i++) if(i+pre_dir!=3){
        int tx=x+dir[i][0],ty=y+dir[i][1];
        if(!check(tx,ty)){
            swap(g[x][y],g[tx][ty]);
            if(dep+evaluate()<=maxdep) IDAstar(tx,ty,dep+1,i,maxdep);
            swap(g[x][y],g[tx][ty]);
            if(ans) return;
        }
    }
}

int main(){
    int sx,sy;
    for(register int i=1;i<=3;i++){
        for(register int j=1;j<=3;j++){
            scanf("%1d",&g[i][j]);
            if(!g[i][j]) sx=i,sy=j;
        }
    }
    if(!evaluate()){ puts("0"); goto Lzs; }
    for(register int maxdep=1;;maxdep++){
        IDAstar(sx,sy,0,-1,maxdep);
        if(ans){ printf("%d\n",maxdep); break; }
    }
    Lzs:;
    return 0;
}
posted @ 2019-05-16 20:51  修电缆的建筑工  阅读(247)  评论(0编辑  收藏  举报