第216题 小R的蛋糕分享

131 阅读3分钟

问题描述

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

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

解题思路

1. 问题分解

要解决问题,我们需要:

  1. 快速计算正方形区域的美味度之和:通过前缀和预处理,快速获得任意子矩形的美味度总和;
  2. 枚举所有可能的正方形切割方式:尝试每一种正方形切割,并计算剩余部分的美味度,找到最优解;
  3. 更新最小差值:逐步记录最优解。

根据这个思路,可以将问题拆分为以下几部分:

  1. 通过切割出一个正方形子矩形计算其美味度和 s1s_1​。
  2. 计算剩余部分的美味度和 s2=total_sums1s_2 = \text{total\_sum} - s_1
  3. 找到使 s1s2|s_1 - s_2| 最小的切割方案。

2. 前缀和优化

为了快速计算任意子矩形的美味度和,使用 前缀和数组,定义如下:

prefix_sum[i][j]=从矩形左上角(1,1)(i,j)的所有元素之和{prefix\_sum}[i][j] = \text{从矩形左上角}(1,1)\text{到}(i,j)\text{的所有元素之和}

前缀和公式:

prefix_sum[i][j]=a[i1][j1]+prefix_sum[i1][j]+prefix_sum[i][j1]prefix_sum[i1][j1]{prefix\_sum}[i][j] = a[i-1][j-1] + {prefix\_sum}[i-1][j] + {prefix\_sum}[i][j-1] - {prefix\_sum}[i-1][j-1]

使用前缀和,可以快速计算任意矩形区域的美味度和。例如,子矩形的左上角为 (x1,y1)(x_1, y_1),右下角为 (x2,y2)(x_2, y_2),其总和为:

sum=prefix_sum[x2][y2]prefix_sum[x11][y2]prefix_sum[x2][y11]+prefix_sum[x11][y11]{sum} = {prefix\_sum}[x_2][y_2] - {prefix\_sum}[x_1-1][y_2] - {prefix\_sum}[x_2][y_1-1] + {prefix\_sum}[x_1-1][y_1-1]


3. 枚举正方形切割

枚举所有可能的正方形切割方式:

  • 正方形左上角坐标:枚举 (i,j)(i, j),其中 1in,1jm1 \leq i \leq n, 1 \leq j \leq m

  • 正方形边长 ll:枚举满足正方形范围不超出矩形边界的边长 ll。对每个左上角 (i,j)(i, j),最大边长为 min(ni+1,mj+1)\min(n-i+1, m-j+1)

  • 计算子矩形美味度:利用前缀和公式计算正方形区域的美味度 s1s_1,剩余部分的美味度为 s2=total_sums1s_2 = \text{total\_sum} - s_1​。

  • 更新最小差值:记录 s1s2|s_1 - s_2| 的最小值。

目标是找到使 s1s2|s_1 - s_2| 最小的切割方案。


4. 实现细节

代码

def solution(n, m, a):
    # 构造前缀和
    prefix_sum = [[0] * (m + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            prefix_sum[i][j] = (
                a[i - 1][j - 1] +
                prefix_sum[i - 1][j] +
                prefix_sum[i][j - 1] -
                prefix_sum[i - 1][j - 1]
            )
    
    # 总美味度
    total_sum = prefix_sum[n][m]
    min_diff = float('inf')  # 初始化最小差值

    # 枚举所有正方形子矩形
    for i in range(1, n + 1):  # 左上角行号
        for j in range(1, m + 1):  # 左上角列号
            for l in range(1, min(n - i + 1, m - j + 1) + 1):  # 正方形边长
                # 子矩形右下角
                x2, y2 = i + l - 1, j + l - 1
                # 计算子矩形的美味度和 s1
                s1 = (
                    prefix_sum[x2][y2]
                    - prefix_sum[i - 1][y2]
                    - prefix_sum[x2][j - 1]
                    + prefix_sum[i - 1][j - 1]
                )
                # 剩余部分的美味度和
                s2 = total_sum - s1
                # 更新最小差值
                min_diff = min(min_diff, abs(s1 - s2))

    return min_diff

复杂度分析

  1. 前缀和计算:
    构造前缀和数组需要 O(n×m)O(n \times m) 时间。

  2. 枚举正方形切割:
    枚举左上角位置 (i,j)(i, j) 的复杂度为 O(n×m)O(n \times m)。对于每个位置,正方形边长最多为 min(n,m)min(n, m),因此复杂度为:

    O(n×m×min(n,m))O(n \times m \times \min(n, m))

  3. 总复杂度:

    O(n×m+n×m×min(n,m))O(n2×m)O(n \times m + n \times m \times \min(n, m)) \approx O(n^2 \times m)

  4. 空间复杂度:
    前缀和数组需要 O(n×m)O(n \times m) 空间。


测试用例

示例 1

输入:

n=3,m=3,a=[[1,2,3],[2,3,4],[3,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]]n = 4, m = 4, a = [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]

输出:2

示例 3

输入:

n=2n=2 , m=2m=2 , a=[[5,5],[5,5]]a=[[5,5],[5,5]]

输出:10


总结

通过前缀和优化和暴力枚举正方形,问题得以高效解决。虽然复杂度较高,但在数据范围内可以运行。进一步优化可以考虑动态规划或限制搜索空间以减少枚举次数。