📅  最后修改于: 2023-12-03 15:07:17.517000             🧑  作者: Mango
给定一张 $N \times M$ 的纸,你需要剪成尽可能少的正方形。当然,剪的时候只能沿着纸的边缘剪,且只能剪直线,不能弯曲。
给定一张 $N \times M$ 的纸,剪成尽可能少的正方形。
输入两个整数 $N$ 和 $M$,表示纸的长和宽。
输出一个整数,表示剪出正方形的最小数目。
2 2
1
2 3
3
$1 \le N, M \le 10^5$
对于这个问题,我们的第一想法可能是暴力枚举。对于每一个正方形,我们都尝试对其进行剪裁,然后统计剩下的块数。显然,这个算法的时间复杂度是 $O(N^5)$ 的,无法通过本题。
我们考虑优化这个算法。由于正方形相对于其它形状是具有特殊性质的,所以我们可以根据这个性质设计一个动态规划算法。
我们设 $f(i, j)$ 表示将 $i \times j$ 的矩形剪成最少的正方形数。那么对于一个 $i \times j$ 的矩形,我们可以将其划分成若干个小矩形进行剪裁,而其中必然包括至少一个正方形。因此,我们可以假设此时最后切割出的正方形的边长为 $k$,那么我们可以将这个大矩形剪成 $i \times j-k \times k$,并将其中的小矩形继续进行剪裁。最终,我们的状态转移方程为:
$$ f(i, j) = 1 + \min { f(i-k, j) + f(k, j-k) } \quad (0 < k < i) $$
这个状态转移方程的含义为,我们尝试在 $i \times j$ 的矩形中剪裁出一个 $(i-k) \times j$ 的大矩形和一个 $k \times (j-k)$ 的小矩形,然后分别对其进行递归剪裁,其中的 $1$ 表示这个过程中我们至少要剪一刀来获取一个正方形。
状态转移方程中,因为从大到小进行递归剪裁不会出现重复计算的问题,所以我们可以直接使用递归方法对其进行求解。
def min_squares(n: int, m: int) -> int:
# 初始化备忘录
memo = [[0] * (m+1) for _ in range(n+1)]
# 递归求解
def dfs(i: int, j: int) -> int:
if i == j: return 1
if memo[i][j]: return memo[i][j]
# 枚举 k 进行剪裁
res = float('inf')
for k in range(1, i):
res = min(res, dfs(i-k, j) + dfs(k, j-k))
for k in range(1, j):
res = min(res, dfs(i, j-k) + dfs(i-k, k))
memo[i][j] = res
return res
return dfs(n, m)
参考链接:剪成最少正方形的纸 | 2套