1. 问题描述:
给定一个长度为 n 的整数数列 a1,a2,…,an,请你选择 k 个数对 [l1,r1],[l2,r2],…,[lk,rk],要求所选数对满足:
1 ≤ l1 ≤ r1 < l2 ≤ r2 < … < lk ≤ rk ≤ n;对于 1 ≤ i ≤ k,ri − li + 1 = m 均成立。设 sum = ∑i=1 k ∑j=li ri aj,sum 的值应尽可能大,请你输出 sum 的最大可能值;
输入格式
第一行包含三个整数 n,m,k;第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示 sum 的最大可能值。
数据范围
前 6 个测试点满足 1 ≤ m × k ≤ n ≤ 20;
所有测试点满足 1 ≤ m × k ≤ n ≤ 5000,0 ≤ ai ≤ 10 ^ 9
输入样例1:
5 2 1
1 2 3 4 5
输出样例1:
9
输入样例2:
7 1 3
2 10 7 18 5 33 0
输出样例2:
61
来源:https://www.acwing.com/problem/content/description/4381/
2. 思路分析:
分析题目可以知道实际上是给我们一个数轴,我们需要在数轴上选择恰好 k 个长度为 m 的不相交区间,并且使得选出的区间和最大,因为 n 最大是 5000,所以需要将时间复杂度控制在 O(n ^ 2),对于这种题目直接上感觉可以使用贪心或者动态规划求解,我们可以先尝试一下动态规划能否解决,仔细分析发现可以使用 dp 来解决,而且这道题目属于不是特别复杂的 dp 问题;dp 问题一般需要考虑两个方面:① 状态表示;② 状态计算;首先考虑状态表示,一般来说以 i 为右端点需要一维(前 i 个数,前 i 个位置...),题目中每一个需要的限制对应一维(动态变化的参数),由题目可知我们最多选择 k 个区间,这个限制对应一维,所以由上面的分析可以知道我们可以声明一个二维数组或者二维列表(python)f,其中 f[i][j] 表示所有最后一个区间的右端点为 i,且一共选择了 j 个区间的所有方案的集合的最大值;然后考虑状态计算,状态计算一般对应集合的划分(将f[i][j] 划分为若干个集合),一般是找最后一个不同点,由于最后一个区间的右端点为 i ,所以最后一个区间是固定的那么我们就需要看倒数第二个区间,我们可以根据倒数第二个区间右端点的位置将 f[i][j] 划分为若干个集合求解每一个集合的最大值就是当前 f[i][j] 的最大值,由于需要选择 j 个区间所以倒数第二个区间的右端点的位置最小值为 (j - 1)* m,最大值为 i - m,所以以倒数第二个区间右端点的位置 k 将 f[i][j] 划分为若干个集合,k 从(j - 1)* m 到 i - m 的位置,在这些集合中求解最大值那么就是当前 f[i][j] 的最大值,可以参照下图:
但是上面这样做枚举状态需要 O(n ^ 2)的计算量(枚举右端点为 O(n),枚举选择的区间数目为 O(n)),状态计算需要枚举倒数第二个区间右端点的位置 k 所以计算量也为 O(n),所以时间复杂度为 O(n ^ 3),由于 n = 5000 所以上面这样写代码肯定是超时的,我们需要将上面的思路优化一下优化掉一维将时间复杂度控制在 O(n ^ 2) 以内才可以通过, 我们可以考虑换一下枚举的顺序,先枚举一共选择的区间数目 j,然后再枚举右端点 i,交换枚举顺序之后可以发现右端点的位置是不断增加的,我们可以声明一个变量 maxf 维护以当前的右端点 i 的最大值,这样就可以维护以 i 为右端点选择了 j 个区间的最大值,那么就可以省略掉一维,时间复杂度为 O(n ^ 2) 就可以通过了;最后我们需要枚举所有以 i 为右端点选择了 k 个区间的最大值求解答案即可。
3. 代码如下:
python:
class Solution:
def process(self):
n, m, k = map(int, input().split())
a = list(map(int, input().split()))
INF = 10 ** 10
f = [[-INF] * (k + 10) for i in range(n + 10)]
s = [0] * (n + 10)
# 计算前缀和
for i in range(1, n + 1):
s[i] = s[i - 1] + a[i - 1]
# 初始状态, 一开始选择0个区间的最大值肯定是0
for i in range(n + 10):
f[i][0] = 0
# 先枚举选择j个区间再枚举右端点的位置i, 枚举的时候由于右端点的位置i是不断增加的所以可以使用一个变量 maxf 来维护到当前右端点的最大值
for j in range(1, k + 1):
maxf = 0
for i in range(j * m, n + 1):
maxf = max(maxf, f[i - m][j - 1] + s[i] - s[i - m])
f[i][j] = maxf
res = 0
# 枚举所有以当前i为右端点选择了k个区间的最大和
for i in range(1, n + 1):
res = max(res, f[i][k])
return res
if __name__ == '__main__':
print(Solution().process())
go:
package main
import "fmt"
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
var (
n, m, k int
)
fmt.Scan(&n, &m, &k)
a := make([]int, n)
s := make([]int, n+10)
// 计算前缀和
for i := 1; i <= n; i++ {
fmt.Scan(&a[i-1])
s[i] = s[i-1] + a[i-1]
}
// 声明一个f[n+10][k+10]的二维列表, 使用make函数分配内存空间
f := make([][]int, n+10)
for i := range f {
f[i] = make([]int, k+10)
}
INF := 1000000000
for i := 0; i < n+10; i++ {
for j := 0; j < k+10; j++ {
if j == 0 {
// 选择了0个区间最大值肯定是0
f[i][j] = 0
} else {
f[i][j] = -INF
}
}
}
for j := 1; j <= k; j++ {
maxf := 0
for i := j * m; i <= n; i++ {
maxf = max(maxf, f[i-m][j-1]+s[i]-s[i-m])
f[i][j] = maxf
}
}
res := 0
for i := 1; i <= n; i++ {
res = max(res, f[i][k])
}
fmt.Println(res)
}