1. 问题描述:
6x6的方格,沿着格子的边线剪开成两部分。要求这两部分的形状完全相同。如图就是可行的分割法。
来源:http://oj.ecustacm.cn/problem.php?id=1320
2. 思路分析:
① 分析题目可以知道我们需要求解对称分割的方案,而且需要计算出总的分割方案数目,对于这种需要尝试可能的方案并且需要计算出总的方案数目的都是可以使用递归解决,使用递归可以搜索出所有的方案即可,而且递归可以解决连通的问题,也就是在搜索的过程中实现多个相邻方块的连接,特别适用于连通性的检测,扩散性的问题(岛屿问题、水洼问题就是典型的连通性与扩散的问题)。而这道题目就是多个相邻位置的连接。分析题目可以知道这道题目的难点在于我们需要找出一个合适的递归方案使得最终剪成的两部分是对称的,一个比较合适的方法是从方格的中心点位置(3,3)开始往两边剪,可以自己模拟一下剪这样的格子如何剪才可以使得对称,可以发现我们从中心点(3, 3)开始剪取,其中一边可以往上下左右方向进行剪取,另外一边也是可以往上下左右方向进行剪取,只有保证两边剪取的坐标是对称的也就是每一次剪取的对应两个坐标都是对称的最终的剪取那么就是对称的,如果当前的坐标为(x, y),那么另外一个坐标肯定是(6 - x,6 - y),其实举一个简单的例子就可以知道,一开始的坐标为(3, 3),一边往上剪的坐标为(2,3),一般往下剪(4,3),所以这个坐标是关于(6,6)对称的。剪到什么时候为止呢?肯定是其中两边遇到了方格的边界,说明这一条对称的分割线已经形成(遇到格子的边界那么说明对称分割线已经形成就可以停止剪取了),其实这道题目巧妙的点就在于使用递归对称分割中对称的分割线的形成过程,看下面的例子就可以知道了,从(3,3)开始剪取然后一直到(0,3)和(6,3)那么分为左右两边的对称线就形成了。
② 因为是二维平面的dfs搜索,所以需要记录已经访问过的位置这样才不至于在搜索周围四个方向的时候出现重复访问已经访问过的位置导致递归一直卡在那里,可以声明一个二维列表表示上下左右四个方向,这样在递归的时候可以使用for循环中尝试四个方向会比较好操作。而且需要注意的一点是因为6 * 6格子是对称的,所以我们在尝试剪的时候其实是对于每一种相同的剪的方案是多计算了3次,其实也不难理解,例如像上面分为左边3 * 6,右边也是3 * 6的格子的时候最终是可以上/下,下/上,左/右,右/左进行的,所以是多计算了3次,最终需要将结果除以4即可
3. 代码如下:
from typing import List
res = 0
pos = [[0, 1], [0, -1], [-1, 0], [1, 0]]
def dfs(x: int, y: int, vis: List[List[int]]):
# 使用global关键字来修改全局变量的值
global res
if x <= 0 or x >= 6 or y <= 0 or y >= 6:
# 当到达边界的时候说明一条对称分割线已经完成此时计数加1
res += 1
return
for i in range(4):
# 尝试上下左右四个方向
x0, y0 = x + pos[i][0], y + pos[i][1]
# 遇到边界说明对称分割线已经形成
if vis[x0][y0] == 0 and vis[6 - x0][6 - y0] == 0:
# 标记已经访问过的位置
vis[x0][y0] = vis[6 - x0][6 - y0] = 1
dfs(x0, y0, vis)
# 回溯, 恢复会当前未剪取的状态这样才可以尝试下一种可能的剪取方式
vis[x0][y0] = vis[6 - x0][6 - y0] = 0
if __name__ == '__main__':
# 声明7 * 7的二维列表在判断的时候方便操作不用考虑下标的问题
vis = [[0] * 7 for i in range(7)]
vis[3][3] = 1
# 使用最中间的位置(3, 3)开始对称搜索
dfs(3, 3, vis)
# 将每一种方案多计算3次的去除掉
print(res // 4)