4418 选元素(递推)

1. 问题描述:

给定一个长度为 n 的整数序列 a1,a2,…,an,请你从中挑选 x 个元素,要求:

  1. 原序列中的每一个长度为 k 的连续子序列都至少包含一个被选中的元素。
  2. 满足条件 1 的前提下,所选 x 个元素的相加之和应尽可能大。

输出最大可能和。

输入格式

第一行包含三个整数 n,k,x,第二行包含 n 个整数 a1,a2,…,an。

输出格式

如果无法满足题目要求,则输出 −1,否则,输出一个整数,表示所选元素的最大可能和。

数据范围

前三个测试点满足 1 ≤ k,x ≤ n ≤ 6
所有测试点满足 1 ≤ k,x ≤ n ≤ 200,1 ≤ ai ≤ 10 ^ 9

输入样例1:

5 2 3
5 1 3 10 1

输出样例1:

18

输入样例2:

6 1 5
10 30 30 70 10 10

输出样例2:

-1

输入样例3:

4 3 1
1 100 1 1

输出样例3:

100
来源:https://www.acwing.com/problem/content/description/4421/

2. 思路分析:

分析题目可以知道我们需要恰好选择 x 个元素,并且满足任意连续的 k 个元素至少包含一个选择的元素,可以发现方案数目是非常多的,Cnx 是一个指数级别的数字,所以我们需要考虑贪心或者动态规划解决,直觉上感觉可以使用 dp 来解决,由于 n 最大是 200,所以需要将时间复杂度控制到 O(n ^ 3) 以内即可,由于选择数字的时候是有顺序的所以属于序列类型的 dp(选择的时候没有顺序的属于背包类型 dp);dp 一般需要考虑两个方面:状态表示和状态计算;首先我们考虑状态表示,一般来说第一维是前 i 个数... ,后面是根据题目的限制确定有几维,一般来说有多少个限制就添加几维(限制条件),由于恰好选择 x 个数字所以选择的数字个数的限制需要一维,所以分析题目的限制可以知道我们可以定义一个二维的数组或者列表 f,其中 f(i,j) 表示所有从前 i 个数中选择 j 个数且选择了第 i 个数的所有方案的集合的最大值;因为需要保证任意连续 k 个数字中有一个数字被选择,这个条件等价于没有被选的数字的长度不超过 k - 1,所以需要保证任意两个被选择的数字之间的距离小于等于 k,所以我们需要知道哪些数字被选了;然后我们需要考虑怎么样进行状态计算,状态计算一般对应集合的划分,我们需要将当前的集合 f[i][j] 划分为若干个不同的子集,由于这道题目需要求解最大值,所以划分的子集只要不遗漏就行,划分子集一般是找最后一个不同点,由于 f[i][j] 最后一个数字是一定被选的,所以最后一个数字是固定的,所以我们看倒数第二个选择数字的位置,根据倒数第二个数字的位置 u 将 f[i][j] 划分为若干个不同的子集,由于需要满足任意连续的 k 个元素中至少需要包含一个被选的元素,所以倒数第二个位置 u 最小需要从 i - k 开始,最大为 i - 1,对于倒数第二个位置 u ,f[i][j] = max(f[i][j],f[u][j - 1] + v),其中 v 为 ai;由于需要枚举 n ^ 2 个状态(第一维枚举前 i 个数字,第二维枚举选择了 j 个数字),状态计算需要枚举选择的倒数第二个数字的位置 u,所以总的时间复杂度为 O(n ^ 3) 是可以通过的,而且可以发现每一次枚举的时候 j 固定的时候每一次对于 i 来说求解 i - k ~ i - 1 的最大值,我们可以先枚举 j ,然后使用单调队列求解一个滑动窗口的最值这样就可以将时间复杂度由 O(n ^ 3) 优化到 O(n ^ 2);还有一个问题是如何初始化呢?因为求解的是最大值所以我们不希望不合法的状态去更新答案,所以一开始的时候可以将所有状态设置为负无穷,并且考虑边界状态 f(0,0),可以发现 f(0,0) 不好解释具体的含义(强行解释从前 0 个数中选并且选择了 0 个数字的最大值为 0),我们可以不解释具体的状态表示的含义,只需要保证能够正确计算出其余的状态即可,可以发现 f(0,0) = 0 就可以正确计算其余的状态:f(1,1),f(2,1),f(3,1)... f(k,1) ,只需要保证能够正确计算其余的状态即可,如果计算答案呢?我们需要枚举一下,枚举所有可能的选择最后一个数字的状态:f(n,x),f(n - 1,x),...f(n - k + 1,x):

3. 代码如下:

python:

class Solution:
    def process(self):
        n, k, m = map(int, input().split())
        a = list(map(int, input().split()))
        INF = 10 ** 12
        f = [[-INF] * (m + 10) for i in range(n + 10)]
        # f[0][0]比较难解释具体的含义, 这里令其等于0是为了后面可以正确计算其他状态的值
        f[0][0] = 0
        for i in range(1, n + 1):
            for j in range(1, m + 1):
                for u in range(max(0, i - k), i):
                    f[i][j] = max(f[i][j], f[u][j - 1] + a[i - 1])
        res = -1
        # 枚举前i个数字并且选择了第i个数字, 恰好选择了m个数字的方案, 在这些方案中取一个max
        for i in range(n - k + 1, n + 1):
            res = max(res, f[i][m])
        print(res)


if __name__ == '__main__':
    Solution().process()

go:

package main

import "fmt"

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func main() {
	var n, k, m, v int
	fmt.Scan(&n, &k, &m)
	// 为f分配内存空间
	f := make([][]int, n+10)
	for i := 0; i < n+10; i++ {
		f[i] = make([]int, m+10)
	}
	INF := 100000000000
	for i := 0; i < n+10; i++ {
		for j := 0; j < m+10; j++ {
			f[i][j] = -INF
		}
	}
	f[0][0] = 0
	for i := 1; i <= n; i++ {
		fmt.Scan(&v)
		for j := 1; j <= m; j++ {
			for u := max(0, i-k); u < i; u++ {
				f[i][j] = max(f[i][j], f[u][j-1]+v)
			}
		}
	}
	res := -1
	for i := n - k + 1; i <= n; i++ {
		res = max(res, f[i][m])
	}
	fmt.Println(res)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值