📜  剪成最少正方形的纸| 2套(1)

📅  最后修改于: 2023-12-03 15:07:17.517000             🧑  作者: Mango

题目背景

给定一张 $N \times M$ 的纸,你需要剪成尽可能少的正方形。当然,剪的时候只能沿着纸的边缘剪,且只能剪直线,不能弯曲。

问题描述

给定一张 $N \times M$ 的纸,剪成尽可能少的正方形。

输入格式

输入两个整数 $N$ 和 $M$,表示纸的长和宽。

输出格式

输出一个整数,表示剪出正方形的最小数目。

输入样例1
2 2
输出样例1
1
输入样例2
2 3
输出样例2
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套