洛谷 P1379 八数码难题 - 一次脑瘫事件
洛谷 P1379 八数码难题 - 一次脑瘫事件 - 关于广搜八数码问题一个隐秘的 Bug
注:本篇首发于作者洛谷博客,作同步处理
(主要是一开始没东西发,先转个发过的(
好吧我真不知道我能遇到这样的bug
ps:这篇不介绍广搜思路,仅吐槽单个bug.
1. 首先贴一下原题
P1379 八数码难题
题目描述
在 \(3 \times 3\) 的棋盘上,摆有八个棋子,每个棋子上标有 \(1\) 至 \(8\) 的某一数字。棋盘中留有一个空格,空格用 \(0\) 来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为 \(123804765\) ),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
输入格式
输入初始状态,一行九个数字,空格用 \(0\) 表示。
输出格式
只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数。保证测试数据中无特殊无法到达目标状态数据。
样例 #1
样例输入 #1
1 |
|
样例输出 #1
1 |
|
提示
样例解释
图中标有 \(0\) 的是空格。绿色格子是空格所在位置,橙色格子是下一步可以移动到空格的位置。如图所示,用四步可以达到目标状态。
并且可以证明,不存在更优的策略。
2. 分析
一眼丁真, 最少移动步数,鉴定为广搜。
至于判重,声明一个9维数组vis,利用桶的思想我们执行
if(vis[s[0]][s[1]][s[2]]...[s[8]])
?虽然可以 \(O(1)\) 时间判重,但是 9
维数组每一维都要包含 9 个元素,总共 \(9^9=387420489\)
项,太多了,数组开不了这么大。
注意到可以把 9 个数字从左到右、从上到下排成排列数,鉴定为康托展开,再写个逆康托展开 。这样就只有 \(9!=362880\) 项,数组存得了了。至于康托展开和逆康托展开是什么,可以看看百度词条。
貌似排成一排之后还有一个特色:根据字符串存储状态,设空格当前位置为 \(P\) ,则有:
- 空格向上移动:空格位置减 3,即交换 \(P\) 和 \(P-3\) 的字符
- 空格向左移动:空格位置减 1,即交换 \(P\) 和 \(P-1\) 的字符
- 空格向右移动:空格位置加 1,即交换 \(P\) 和 \(P+1\) 的字符
- 空格向下移动:空格位置加 3,即交换 \(P\) 和 \(P+3\) 的字符
如果设规则编号为 \(k\),则上述四条规则可归纳为一条: 交换 $P$ 和 $P+(2*k-5)$ 的字符,照这样就好写状态转移了。
直接开写!
1 |
|
诶,感觉还行,测个样例:
1 |
|
噫!好!我中了!!直接交罢:
啊???怎么会事呢?测错误点看看:
1 |
|
嗯……改成用 set 和 char 数组试试,可能是康托展开(或者逆康托展开)或者是 string 的毛病吧。
*小改
1 |
|
再试试样例:
1 |
|
嗯……不好说,测个之前的错误点吧:
1 |
|
……好吧,看来不是康托展开(或逆康托展开)的错……
并且写的时候调试过康托展开和逆康托展开,确实没错啊…… string 也应该不会错吧……
*于是若智的我盯着代码看了整整两天都没找到 bug ,两天后:
算了,去看看讨论吧:
“你程序的tx有问题, 比如如果'0'在边界上的话, 就不能向左移了,如下例:
1 2 3 0 4 5 6 7 8
这个例子的空格就不能向左移”
!!!!!!!!!!!!!!!!!!!!!!!!
千想万想没想到这一层——存储与实际的差别:在用一维数组存储和状态转移的时候,\(1\ 2\ 3\ 0\ 4\ 5\ 6\ 7\ 8\) 把 \(3\) 和 \(5\) 交换变成 \(1\ 2\ 0\ 3\ 4\ 5\ 6\ 7\ 8\) 看来是显而易见符合这个精炼的公式: 交换 $P$ 和 $P+(2*k-5)$ 的字符,但是在二维里面却是不合理的移动!
于是我只用了 2 分钟加一点判断就改好了困扰我两天的 bug :
1 |
|
顺利的过了样例和错误点:
1 |
|
也AC了。
3. 尾声
*去世