📌  相关文章
📜  通过从数组 [l, r] 中选择一个子段来最大化总和,并将 arr[i] 转换为 (M–arr[i]) 最多一次(1)

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

介绍

本题要求通过选择数组中的子段来最大化总和,并且可以将任意一个元素转换为M减去该元素的值,但最多只能操作一次。

这是一个经典的区间动态规划问题,可以通过状态转移方程来求解,同时利用前缀和等辅助数据结构可以提高算法的效率。

解题思路

设$dp_{i,0}$表示前$i$个元素中至少不用转换就可以获得的最大的子段和,$dp_{i,1}$表示前$i$个元素中可以进行一次转换后获得的最大子段和。

则有状态转移方程:

$dp_{i,0}=\max(dp_{i-1,0},0)+arr[i]$

$dp_{i,1}=\max(dp_{i-1,1}+arr[i]-M,dp_{i-1,0}-arr[i]+M)$

其中$dp_{i,0}$的计算较为简单,表示前$i$个元素中至少不用转换就可以获得的最大子段和。如果前$i-1$个元素中的最大子段和加上当前元素的值是正数,则可以将当前元素加入之前的最大子段中,否则就应该从当前位置重新开始计算一个新的子段。

而$dp_{i,1}$则需要考虑可以进行一次转换的情况。假设在前$i-1$个元素中最大子段和出现在区间$[l,r]$,则分为两种情况:

  • 在$l$位置之前同时将某个数转换为$M-arr[i]$,则最大值为$dp_{i-1,1}$
  • 在$l$位置或之后将某个数转换为$M-arr[i]$,则最大值为$dp_{i-1,0}-arr[i]+M$

对于最终答案,应取$dp_{n,0}$和$dp_{n,1}$的最大值。

具体实现中可以使用两对循环分别计算$dp_{i,0}$和$dp_{i,1}$,时间复杂度为$O(n^2)$。但由于其中包含了大量冗余计算,可以在计算时使用前缀和等辅助数据结构来减少无意义的重复计算,从而优化时间复杂度至$O(n)$。

代码实现
def max_sum_subarray(arr, M):
    n = len(arr)
    dp0, dp1 = [0] * n, [0] * n
    max_sum = 0
    prefix_sum = [0] * (n+1)
    for i in range(1, n+1):
        prefix_sum[i] = prefix_sum[i-1] + arr[i-1]
    for i in range(n):
        if i == 0:
            dp0[i] = arr[i]
            dp1[i] = max(arr[i], M-arr[i])
        else:
            dp0[i] = max(dp0[i-1], 0) + arr[i]
            dp1[i] = max(dp1[i-1] + arr[i] - M, dp0[i-1] - arr[i] + M)
            if i > 1:
                dp1[i] = max(dp1[i], prefix_sum[i] - min(prefix_sum[1:i], default=0) - M + dp0[i-2])
        max_sum = max(max_sum, dp0[i], dp1[i])
    return max_sum

其中prefix_sum是前缀和数组,min(prefix_sum[1:i], default=0)表示区间$[1,i)$中的最小前缀和,通过减去该值来完成对当前位置之前进行拆分的操作。