青训营X豆包MarsCode 技术训练营之小R的蛋糕分配 | 豆包MarsCode AI 刷题

71 阅读4分钟

题目描述

小R手里有一个大小为 n 行 m 列的矩形蛋糕,每个小正方形区域都有一个代表美味度的整数。小R打算切割出一个正方形的小蛋糕给自己,而剩下的部分将给小S。她希望两人吃的部分的美味度之和尽量接近。

我们定义小R吃到的部分的美味度之和为 s_1,而小S吃到的部分的美味度之和为 s_2,请你帮助小R找到一个切割方案,使得 |s_1 - s_2| 的值最小。


测试样例

样例1:

输入:n = 3, m = 3, a = [[1, 2, 3], [2, 3, 4], [3, 2, 1]]
输出:1

样例2:

输入:n = 4, m = 4, a = [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]
输出:2

样例3:

输入:n = 2, m = 2, a = [[5, 5], [5, 5]]
输出:10

问题分析

输入与输出:
  1. 输入:

    • 蛋糕的大小 n 行 m 列。
    • 一个 n*m的整数矩阵 a,表示每块蛋糕的美味度。
  2. 输出:

    • 一个整数,表示最小可能的差值 ∣s1−s2∣。

解题思路

为了找到最佳的切割方案,我们可以分为以下几个步骤:

1. 总美味度计算

首先计算蛋糕的总美味度 S,可以通过累加矩阵中所有元素得到。如果分给小R的美味度为 s1,分给小S的美味度为 s2=Ss1s2=S−s1,目标是最小化:

s1s2=2s1S∣s1−s2∣=∣2s1−S∣
2. 前缀和的妙用

为了快速求出任意子矩形区域的美味度和,我们使用二维前缀和

  • 构建一个二维数组 prefix[i][j],其中:
prefix[i][j]=x=1iy=1ja[x][y]prefix[i][j]=\sum_{x=1}^{i} \sum_{y=1}^{j} a[x][y]
  • 利用前缀和快速计算子矩形 (x1,y1)到 (x2,y2)的美味度:
sum=prefix[x2][y2]prefix[x11][y2]prefix[x2][y11]+prefix[x11][y11]sum=prefix[x2][y2]prefix[x11][y2]prefix[x2][y11]+prefix[x11][y11]sum=prefix[x2][y2]prefix[x11][y2]prefix[x2][y11]+prefix[x11][y11]sum=prefix[x2][y2]−prefix[x1−1][y2]−prefix[x2][y1−1]+prefix[x1−1][y1−1]\text{sum} = \text{prefix}[x_2][y_2] - \text{prefix}[x_1-1][y_2] - \text{prefix}[x_2][y_1-1] + \text{prefix}[x_1-1][y_1-1]sum=prefix[x2​][y2​]−prefix[x1​−1][y2​]−prefix[x2​][y1​−1]+prefix[x1​−1][y1​−1]
3. 枚举所有正方形区域

对于正方形的切割方案:

  • 枚举正方形的左上角坐标 (x1,y1)。
  • 枚举边长 l,计算右下角坐标为 (x2,y2)的正方形区域。
  • 计算该正方形的美味度 s1,并用总美味度 S 更新最小差值。
4. 更新最优解

每次计算出当前正方形的美味度 s1后,更新最小差值:

min_diff=min(min_diff,2s1S)\text{min\_diff}=min⁡(\text{min\_diff},∣2s1−S∣)

思路总结

  1. 目标:切割正方形,使得两部分美味度尽量接近。

  2. 关键点

    • 快速计算任意正方形区域的美味度和。
    • 遍历所有正方形区域找到最小的美味度差值。
  3. 工具:利用二维前缀和提升效率,避免重复计算。

完整代码

def min_diff(n, m, a):
    # 计算二维前缀和
    prefix = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            prefix[i][j] = (a[i - 1][j - 1]
                            + prefix[i - 1][j]
                            + prefix[i][j - 1]
                            - prefix[i - 1][j - 1])

    # 总美味度
    total_sum = prefix[n][m]
    min_diff = float('inf')
    
    # 枚举所有正方形区域
    for x1 in range(1, n + 1):
        for y1 in range(1, m + 1):
            # 枚举边长
            for l in range(1, min(n - x1 + 1, m - y1 + 1) + 1):
                x2, y2 = x1 + l - 1, y1 + l - 1
                # 子矩形美味度和
                s1 = (prefix[x2][y2]
                      - prefix[x1 - 1][y2]
                      - prefix[x2][y1 - 1]
                      + prefix[x1 - 1][y1 - 1])
                # 剩余部分美味度和
                s2 = total_sum - s1
                # 更新最小差值
                min_diff = min(min_diff, abs(s1 - s2))
    
    return min_diff

# 测试样例
print(min_diff(3, 3, [[1, 2, 3], [2, 3, 4], [3, 2, 1]]))  # 输出: 1
print(min_diff(4, 4, [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]))  # 输出: 2
print(min_diff(2, 2, [[5, 5], [5, 5]]))  # 输出: 10

复杂度分析

  1. 前缀和构建:时间复杂度为 O(nm)。
  2. 正方形区域枚举:最多 O(n2m2)O(n^2 \cdot m^2)次操作。
  3. 总复杂度:对于n,m50 n,m≤50,代码可以在合理时间内运行。

总结

这道题的核心目标是帮助小R找到一块正方形的小蛋糕,使得分给她和小S的美味度和尽可能接近,即最小化两部分美味度之差 ∣s1−s2∣。我们可以通过观察发现,这实际上是一个在矩形范围内寻找最佳正方形区域的优化问题。问题的关键是如何快速计算任意正方形区域的美味度和 s1,并且在所有可能的选择中找到差值最小的结果。为了提高效率,我们可以利用二维前缀和,将正方形区域的美味度求和从逐格累加优化为常数时间复杂度的计算。同时,我们需要遍历所有可能的正方形大小和位置,通过计算 s1的美味度和,动态更新差值 ∣2s1−S∣ 的最小值。这个方法有效地解决了暴力枚举效率低的问题,既保证了正确性,又能应对较大的输入规模。通过这种方式,我们可以快速得出最优解,满足小R的需求,也提升了算法设计的能力。

在解决这道题的过程中,我遇到了一些挑战和思考。最初,题目看似简单,目标是找到一个正方形区域,使得两部分美味度的差值最小化。但随着问题的深入,我发现要在一个 n×m的矩阵中有效地找到最优的正方形切割区域并不是一件容易的事。最直观的解决方法是暴力枚举所有可能的正方形区域,但由于矩阵的规模较大,这种方法会导致效率低下,可能无法在限定时间内完成。

因此,我需要考虑如何优化这个过程。经过分析,我想到使用二维前缀和来快速计算每个正方形区域的美味度和。虽然这一思路能有效地减少计算复杂度,但实现时仍然存在一些细节问题需要处理,例如如何确定正方形区域的边界、如何更新最小差值等。在实现过程中,我也体会到如何将数学模型和算法思维结合,处理矩阵的区域求和问题时,前缀和的技巧提供了很大的帮助。

总体而言,写这道题让我更加深入理解了如何在有限的时间和空间内,通过巧妙的算法优化,解决看似简单却涉及复杂计算的问题。通过这道题,我也学到了如何有效地分解问题,逐步优化算法,避免冗余计算,从而提高程序效率。