小R的蛋糕分享

问题描述

        小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

Python实现

思路说明

        题目要求在给定的 n 行 m 列的矩形蛋糕中,找到一个正方形的小蛋糕,使得小R和小S吃到的部分的美味度之和尽量接近。我们需要计算出所有可能的正方形的美味度之和,并找到使得 |s_1 - s_2| 最小的切割方案。为了高效地计算任意子矩阵的美味度之和,我们可以使用前缀和数组来预处理原始矩阵,从而在 O(1) 时间内计算出任意子矩阵的美味度之和。接着,我们通过二分查找来确定每个位置的最大正方形边长,使得美味度之和满足条件。

解题过程

  1. 前缀和数组构建

    • 我们构建一个 (n+1) x (m+1) 的前缀和数组 s,其中 s[i][j] 表示从 (0,0) 到 (i-1,j-1) 的子矩阵的美味度之和。
    • 通过动态规划的方式,计算出每个位置的前缀和:
      • s[i][j] = a[i-1][j-1] + s[i][j-1] + s[i-1][j] - s[i-1][j-1]
  2. 二分查找确定最大正方形边长

    • 对于每个位置 (i, j),我们通过二分查找确定以该位置为右下角的正方形的最大边长 l,使得正方形的美味度之和 cal(i, j, l) 满足 cal(i, j, l) * 2 <= s[n][m]
    • 二分查找的范围是从 0 到 min(i, j)
  3. 计算最小差值

    • 对于每个位置 (i, j),计算出满足条件的最大正方形边长 l,并计算出 |s[n][m] - cal(i, j, l) * 2| 的值。
    • 如果 l 不是最大边长,还需要检查 l+1 的情况,以确保找到最优解。

复杂度分析

  • 时间复杂度

    • 构建前缀和数组的时间复杂度为 O(n×m)。
    • 对于每个位置 (i, j),二分查找的时间复杂度为 O(log(min(i,j))),总共有 O(n×m) 个位置,因此总时间复杂度为 O(n×m×log(min(i,j)))。
  • 空间复杂度

    • 前缀和数组的空间复杂度为 O(n×m)。

知识点扩展

  • 前缀和

    • 前缀和是一种常用的预处理技术,用于快速计算数组中任意子数组的和。在本题中,我们通过构建二维前缀和数组,可以在 O(1) 时间内计算出任意子矩阵的美味度之和。
  • 二分查找

    • 二分查找是一种高效的查找算法,适用于在有序数组中查找特定值。在本题中,我们通过二分查找来确定每个位置的最大正方形边长,从而避免了暴力枚举所有可能的正方形边长,提高了算法的效率。

代码实现

def solution(n:int,m:int,a:list)->int:
    assert n == len(a) and all(m == len(v) for v in a)
    s = [[0] * (m+1) for _ in range(n+1)]

    def cal(x, y, l):
        return s[x][y] - s[x - l][y] - s[x][y - l] + s[x - l][y - l]

    for i in range(1, n + 1):
        for j in range(1, m + 1):
            s[i][j] = a[i-1][j-1]
            s[i][j] += s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1]

    ans = s[n][m]
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            k = min(i, j)
            l, r = 0, k
            while l < r:
                mid = (l + r + 1) // 2
                if cal(i, j, mid) * 2 <= s[n][m]:
                    l = mid
                else:
                    r = mid - 1
            ans = min(ans, abs(s[n][m] - cal(i, j, l) * 2))
            if l < k:
                ans = min(ans, abs(s[n][m] - cal(i, j, l + 1) * 2))

    return ans


if __name__ == '__main__':
    print(solution(n = 3, m = 3, a = [[1, 2, 3], [2, 3, 4], [3, 2, 1]]) == 1)
    print(solution(n = 4, m = 4, a = [[1, 2, 3, 4], [4, 3, 2, 1], [1, 2, 3, 4], [4, 3, 2, 1]]) == 2)
    print(solution(n = 2, m = 2, a = [[5, 5], [5, 5]]) == 10)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值