n-puzzle问题

数字推盘游戏(n-puzzle)是由一块有凹槽的板和数个写有数字的大小相同的方块所组成。15-puzzle的板上会有15个方块和1个大小相当于一个方块的空位(供方块移动之用)。而8-puzzle为九宫格布局,有8个方块和1个空位。游戏者要移动板上的方块,让所有的方块顺着数字的次序排列。15-puzzle的最优解至多有80步;而8-puzzle的最优解至多有31步。本文介绍了这一类问题有解的条件以及这一类问题的解法。

判断有解

一个 n × m n\times m n×m 的棋盘,当 m = 1 m=1 m=1 时,有且仅有 n n n 个有解的情况;当 n = 1 n=1 n=1 时,有且仅有 m m m 个有解的情况;否则,有且仅有 ( n × m ) ! / 2 (n\times m)!/2 (n×m)!/2 个有解的情况。

证明

m = 1 m=1 m=1 时,有解的情况只能是所有的数按序排列。此时空白格有 n n n 种选择,因此是 n n n 种有解情况。 n = 1 n=1 n=1 的情况同理。

n , m > 1 n,m>1 n,m>1 时,将棋盘上的所有数字格按从左到右、从上到下的顺序填进一个序列 A A A 中。

我们定义一个序列的逆序对数为 ∑ i &lt; j ( a i &gt; a j ) \sum\limits_{i&lt;j}(a_i&gt;a_j) i<j(ai>aj) 。我们假设计算逆序的方法为,从一个数向后看,统计比它小的数有多少,然后再求和。记 A A A 数组的逆序数为 inv \text{inv} inv

首先,我们证明几个引理:

引理1.1.1: m m m 为奇数,则棋盘有解的必要条件为 inv ≡ 2 ( m o d 2 ) \text{inv}\equiv2\pmod 2 inv2(mod2)

空白格的行为只有四种:上、下、左、右。向左或向右都不会改变 A A A 数组,因此我们讨论向上和向下。这两个操作是对称的,我们不妨考虑向上。

向上的操作会使 A A A 中某一个格子向右跳 m − 1 m-1 m1 格(在 A A A 数组中,空白格的上面的格子和空白格右边的格子之间有 m − 1 m-1 m1 个格子)。记这个格子为 t t t ,设 t t t 跳过的这 m − 1 m-1 m1 个格子为 s [ 1.. m − 1 ] s[1..m-1] s[1..m1] ,设 s s s 中有 x x x 个比 t t t 小的格子。

现在我们考虑 t t t 跳完之后整个序列逆序数的变化。首先, t t t 不会对除了 s s s t t t 之外的格子在计算 inv \text{inv} inv 的时候产生影响。其次, t t t 右移后,原本在 s s s 中比它小的现在不能算了,因此 inv \text{inv} inv 要减去 x x x 。最后, s s s 中的格子在计算逆序数的时候,由于多了 t t t 的存在, inv \text{inv} inv 要加上 m − 1 − x m-1-x m1x (有 x x x 个比 t t t 小,就有 m − 1 − x m-1-x m1x 个比 t t t 大)。因此, inv \text{inv} inv 的变化为 Δ inv = m − 1 − 2 x \Delta\text{inv}=m-1-2x Δinv=m12x

同理,向下造成总逆序数的变化为 Δ inv = − m + 1 + 2 x \Delta\text{inv}=-m+1+2x Δinv=m+1+2x

因为 m m m 是奇数,因此 m − 1 − 2 x m-1-2x m12x − m + 1 + 2 x -m+1+2x m+1+2x 都是偶数,即, Δ inv \Delta\text{inv} Δinv 是偶数。我们知道,最终情况的逆序数为 0 0 0 ,是偶数,因此对于所有有解的棋盘 inv \text{inv} inv 都是偶数,证毕。

引理1.1.2: m m m 是偶数,设空白格在第 i i i 行,则棋盘有解的必要条件为 inv ≡ n − i ( m o d 2 ) \text{inv}\equiv n-i\pmod 2 invni(mod2)

设从当前状态到最终状态的过程中,空白格向上移动了 U U U 次,向下移动了 D D D 次,则 n − i = D − U n-i=D-U ni=DU。因为 m m m 是偶数,所以向上或向下移动一次造成的 Δ inv \Delta\text{inv} Δinv 为奇数。设空白格向上移动造成的 Δ inv \Delta\text{inv} Δinv Σ U \Sigma_U ΣU ,向下移动造成的 Δ inv \Delta\text{inv} Δinv Σ D \Sigma_D ΣD ,则 D ≡ Σ D ( m o d 2 ) , U ≡ Σ U ( m o d 2 ) D \equiv \Sigma_D \pmod 2,U \equiv \Sigma_U \pmod 2 DΣD(mod2),UΣU(mod2) ,则 Δ inv = Σ U + Σ D ≡ U + D ≡ D − U ≡ n − i ( m o d 2 ) \Delta \text{inv} =\Sigma_U+\Sigma_D \equiv U+D \equiv D-U \equiv n-i \pmod 2 Δinv=ΣU+ΣDU+DDUni(mod2) ,证毕。

引理1.2: 具有偶数个逆序对数的排列共有 n ! / 2 n!/2 n!/2 个,其中 n n n 是序列的长度。

假设 A A A 的逆序对数是偶数,则当我们交换 A A A 的前两个数, A A A 的逆序对数变为奇数(当 A [ 1 ] &lt; A [ 2 ] A[1]&lt;A[2] A[1]<A[2] 时,逆序对数加 1 1 1 ;否则减 1 1 1 )。因此任意一个具有偶数个逆序对数的排列都有一个唯一的具有奇数个逆序对数的排列与之对应。因此,这两部分各占一半,而总情况有 n ! n! n! 种,因此具有偶数个逆序对数的排列共有 n ! / 2 n!/2 n!/2 个。

因此,当 m m m 为奇数时,使棋盘无解的 A A A 数组至少有 ( n m − 1 ) ! / 2 (nm-1)!/2 (nm1)!/2 种。而对于每一种 A A A 数组,空白格有 n m nm nm 种位置的选择,因此无解的情况至少有 n m × ( n m − 1 ) ! / 2 = ( n × m ) ! / 2 nm\times(nm-1)!/2=(n\times m)!/2 nm×(nm1)!/2=(n×m)!/2 。当 m m m 为偶数时,使棋盘无解的 A A A 数组至少有 ( n m − 1 ) ! / 2 (nm-1)!/2 (nm1)!/2 种。对于每一种 A A A 数组,当 inv \text{inv} inv 为偶数时,需要空白格在行号 i i i 的位置,且满足 n − i n-i ni 为偶数。这种情况占一半,当 inv \text{inv} inv 为奇数时同理,因此依旧至少有 ( n × m ) ! / 2 (n\times m)!/2 (n×m)!/2 种无解的情况。

综上,由引理一,至少有 ( n × m ) ! / 2 (n\times m)!/2 (n×m)!/2 种无解的情况。

引理二: 至多有 ( n × m ) ! / 2 (n\times m)!/2 (n×m)!/2 种无解的情况。

证明过程

因此,我们证明了,棋盘有解的情况有且仅有 ( n × m ) ! / 2 (n\times m)!/2 (n×m)!/2 种。

同时,我们也得出一个判断是否有解的结论:设 A A A 序列的逆序数为 inv \text{inv} inv ,空白格所在的行数为 i i i ,则棋盘有解当且仅当 [ inv ≡ m − 1 ( m o d 2 ) ] ∨ [ inv ≡ n − i ( m o d 2 ) ] [\text{inv}\equiv m-1 \pmod 2]\vee[\text{inv}\equiv n-i \pmod 2] [invm1(mod2)][invni(mod2)]

解法

我们使用迭代加深搜索 IDA* 来解决这一问题。

该算法属于启发式搜索,它使用启发式函数 f = g + h f=g+h f=g+h 来减少无用的搜索。式中 g g g 表示从初始状态到当前状态的“距离”, h h h 表示从当前状态到目标状态的下界“距离”。也就是说,一个结点状态的 f f f 表示选择了这个状态需要的总“距离”的下界。我们使用深度优先搜索,当深度超过了启发式函数的时候,表明走上了“歧途”,立即退出。

下面是杭电上的例题HDU - 1043 Eight,属于八数码问题,题目链接

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
const int dire[4]={'r','l','d','u'};
const int MAXN=41;
int maze[3][3];
int h(){
    int res=0;
    for (int i=0;i<3;++i){
        for (int j=0;j<3;++j){
            if (maze[i][j]==0) continue;
            int x=(maze[i][j]-1)/3;
            int y=(maze[i][j]-1)%3;
            res+=abs(x-i)+abs(y-j);
        }
    }
    return res;
}
string path;
PII cur;
bool dfs(int G,int prev,int F){
    int H=h();
    if (H==0) return true;
    if (G+H>F) return false;
    for (int i=0;i<4;i++){
        if ((i^1)==prev) continue;
        int x=cur.first+dx[i];
        int y=cur.second+dy[i];
        if (x<0||x>2||y<0||y>2) continue;
        PII tmp=cur;
        path.push_back(dire[i]);
        swap(maze[x][y],maze[cur.first][cur.second]);
        cur=make_pair(x,y);
        if (dfs(++G,i,F)) return true;
        cur=tmp;
        swap(maze[x][y],maze[cur.first][cur.second]);
        path.pop_back();
    }
    return false;
}
string ida_star(){
    for (int F=h();F<MAXN;++F)
        if (dfs(0,-1,F)) return path;
}
bool occur[9];
char str[2];
int main(){
    while (1){
        memset(occur,false,sizeof(occur));
        int inv=0;
        for (int i=0;i<3;++i){
            for (int j=0;j<3;++j){
                if (scanf("%s",str)==-1) return 0;
                if (str[0]=='x'){
                    maze[i][j]=0;
                    cur=make_pair(i,j);
                }
                else{
                    sscanf(str,"%d",&maze[i][j]);
                    inv+=count(occur+1,occur+maze[i][j],false);
                    occur[maze[i][j]]=true;
                }
            }
        }
        path.clear();
        if (inv&1) puts("unsolvable");
        else puts(ida_star().c_str());
    }
    return 0;
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值