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 < j ( a i > a j ) \sum\limits_{i<j}(a_i>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 inv≡2(mod2) 。
空白格的行为只有四种:上、下、左、右。向左或向右都不会改变 A A A 数组,因此我们讨论向上和向下。这两个操作是对称的,我们不妨考虑向上。
向上的操作会使 A A A 中某一个格子向右跳 m − 1 m-1 m−1 格(在 A A A 数组中,空白格的上面的格子和空白格右边的格子之间有 m − 1 m-1 m−1 个格子)。记这个格子为 t t t ,设 t t t 跳过的这 m − 1 m-1 m−1 个格子为 s [ 1.. m − 1 ] s[1..m-1] s[1..m−1] ,设 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 m−1−x (有 x x x 个比 t t t 小,就有 m − 1 − x m-1-x m−1−x 个比 t t t 大)。因此, inv \text{inv} inv 的变化为 Δ inv = m − 1 − 2 x \Delta\text{inv}=m-1-2x Δinv=m−1−2x 。
同理,向下造成总逆序数的变化为 Δ 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 m−1−2x 和 − 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 inv≡n−i(mod2)。
设从当前状态到最终状态的过程中,空白格向上移动了 U U U 次,向下移动了 D D D 次,则 n − i = D − U n-i=D-U n−i=D−U。因为 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+ΣD≡U+D≡D−U≡n−i(mod2) ,证毕。
引理1.2: 具有偶数个逆序对数的排列共有 n ! / 2 n!/2 n!/2 个,其中 n n n 是序列的长度。
假设 A A A 的逆序对数是偶数,则当我们交换 A A A 的前两个数, A A A 的逆序对数变为奇数(当 A [ 1 ] < A [ 2 ] A[1]<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 (nm−1)!/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×(nm−1)!/2=(n×m)!/2 。当 m m m 为偶数时,使棋盘无解的 A A A 数组至少有 ( n m − 1 ) ! / 2 (nm-1)!/2 (nm−1)!/2 种。对于每一种 A A A 数组,当 inv \text{inv} inv 为偶数时,需要空白格在行号 i i i 的位置,且满足 n − i n-i n−i 为偶数。这种情况占一半,当 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] [inv≡m−1(mod2)]∨[inv≡n−i(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;
}
n-puzzle问题相关推荐
- 学会在Unity中创建一个Match-3益智游戏 Learn To Create a Match-3 Puzzle Game in Unity
MP4 |视频:h264,1280×720 |音频:AAC,44.1 KHz,2 Ch 语言:英语+中英文字幕(根据原英文字幕机译更准确) |时长:48场讲座(6h 38m) |大小解压后:2.8 G ...
- 【杭电ACM】1097 A hard puzzle
[杭电ACM]1097 A hard puzzle http://acm.hdu.edu.cn/showproblem.php?pid=1097 先用int手写了算法结果竟然wrong answer ...
- Eight puzzle --HOJ 11918
1.题目类型:模拟.哈希表.BFS. 2.解题思路:(1)模拟Eigh Puzzle的变换方式,并记录在数组中 :(2)由于变换的最终结果相同,所以采用反向的BFS遍历所有情况,并记录所有情况:(3) ...
- 补第四周作业总结——8 puzzle
8 puzzle已经提供了解决思路,早期的人工智能算法A.我只能感觉它的神奇,但是没法创造性地使用它.只能按部就班地完成这周的作业. 难点在于对过程的不理解.这个33的格子搜索算法没有尽头,随着步数的 ...
- UVA227 Puzzle
问题链接:UVA227 Puzzle.基础训练级的问题,用C语言编写程序. 问题简述:一个5×5的网格,一个格子是空的,其他格子各有一个字母,一共有四种指令:A,B,L,R,分别表示把空格上.下.左. ...
- UVa10639 Square Puzzle(WA)
例子通过了,并且udebug上的例子也通过了,但是提交还是错误. 针对特殊情况: 3 4 7 0 2 1 2 2 3 3 2 4 2 4 4 0 4 7 0 0 4 0 4 2 3 2 2 1 1 2 ...
- 【37.50%】【codeforces 745B】Hongcow Solves A Puzzle
time limit per test2 seconds memory limit per test256 megabytes inputstandard input outputstandard o ...
- 如何更好的解决问题 : The puzzle of die
人们常说,没有学不好的学生,只有不会教的老师.此言差矣,不同学生的智力不同,成长环境不同,自然会在学力的表现上参差不齐.然而好的教育确实又可以在一定层面上改善这种情况.即,尽量让问题满足大部分人的 m ...
- c++扔鸡蛋问题egg dropping puzzle(附完整源码)
C++扔鸡蛋问题egg dropping puzzle 扔鸡蛋问题egg dropping puzzle算法的完整源码(定义,实现,main函数测试) 扔鸡蛋问题egg dropping puzzle ...
- leetcode 1178. Number of Valid Words for Each Puzzle | 1178. 猜字谜(bitmask位运算)
题目 https://leetcode.com/problems/number-of-valid-words-for-each-puzzle/ 题解 看了答案,堪称力扣最详细的答案,从时间复杂度的角度 ...
最新文章
- 令人迷惑的ATT的jmp:直接跳转和间接跳转 [转]
- c2065 未声明的标识符 解决ok
- 【转载】关于Java堆和栈的解释,收藏下来以后学习
- 常用抓包工具(可编程抓包工具)
- Tomcat虚拟主机搭建Web站点
- Java获取文件路径
- (赞助5本)谷歌官方推荐的 TensorFlow 2 “豹书”来了!
- poj3981 字符串替换-字符串的基本操作
- [蓝桥杯2015决赛]穿越雷区-bfs
- Go 语言实现 23 种设计模式 单例模式
- Hadoop(七)Hive基础
- 约瑟芬公主把乔治放在了第三位,对吧
- Google 投资了京东
- 关于 LiDAR 点云数据处理的一些思考
- QQ文件路径,QQ图片保存地址
- Python 数独求解
- 百度翻译API接口的使用
- LeetCode 848. Shifting Letters
- 跟老杜手撕Spring6教程(三)Spring的入门程序
- 【iOS沉思录】如何招聘一个靠谱的 iOS程序员+面试题详解
热门文章
- 计算机网络基础ensp,计算机网络基础 计算机网络基础 实验三-eNSP路由器基本配置实验-学生结果范例.docx...
- Python基础的学习和简单爬虫的编写
- apollo安装(windows)
- 竞争条件(race condition)
- 一篇文章浅析Django Form组件相关知识
- Spock测试框架如何Mock静态方法
- 安卓简单VMP思路笔记
- win10如何提高电脑画质_教你win10系统提高图片分辨率的修复教程
- 微信定时每天给女友发送甜言蜜语(附代码教程)
- LeetCode 1599. 经营摩天轮的最大利润