【DP】2024E-玩牌高手
【DP】2024E-玩牌高手
题目描述与示例
本题练习地址:https://www.algomooc.com/problem/P3394
题目描述
给定一个长度为n
的整型数组,表示一个选手在n
轮内可选择的牌面分数。选手基于规则选牌。
请计算所有轮结束后其可以获得的最高总分数。
选择规则如下:
- 在每轮里选手可以选择获取该轮牌面,则其总分数加上该轮牌面分数,为其新的总分数。
- 选手也可不选择本轮牌面直接跳到下一轮,此时将当前总分数还原为
3
轮前的总分数,若当前轮次小于等于3
(即在第1
、2
、3
轮选择跳过轮次),则总分数置为0
。 - 选手的初始总分数为
0
,且必须依次参加每一轮
输入描述
第一行为一个小写逗号分割的字符串,表示n
轮的牌面分数,1 <= n <= 20
。
分数值为整数,-100 <= 分数值 <= 100
。
不考虑格式问题。
输出描述
所有轮结束后选手获得的最高总分数。
示例
输入
1,-5,-6,4,3,6,-2
输出
11
示例说明
- 输入的分数数组为
[1, -5, -6, 4, 3, 6, -2]
。 - 考虑最优策略为:
- 第1轮选择分数1:总分为
1
。 - 第2轮跳过:由于在前三轮内,总分重置为
0
。 - 第3轮跳过:总分仍为
0
。 - 第4轮选择分数4:总分从
0
变为4
。 - 第5轮选择分数3:总分从
4
变为7
。 - 第6轮选择分数6:总分从
7
变为13
。 - 第7轮选择分数-2:如果选择跳过,总分回到第
4
轮的总分4
。如果选择牌面,总会从13
变为11
。选择牌面能得到更高的分数。 - 选择这些分数后,最后总分为
11
。
- 第1轮选择分数1:总分为
解题思路
题目要求,按照顺序进行每一轮的分数选择。而每一轮有且只有两个选择
- 选择当前轮次,总分加上当前轮次的分数
- 不选择当前轮次,总分重置为
3
轮之前的分数
无论选还是不选,我们都希望当前轮次能够获得尽可能高的分数。
很显然这可以从动态规划的角度来思考这个问题。
设置dp
数组为长度为n
的数组,dp[i]
表示在第i
轮中可以获得的最大分数。
我们可以很容易地写出动态转移方程
- 若
i >= 3
, 则dp[i] = max(dp[i-1]+nums[i],dp[i-3])
- 若
i < 3
,则dp[i] = max(dp[i-1]+nums[i],0)
其含义是,每一轮能够获得的最大分数,要么是选择这一轮获得的分数再加上上一轮能获得的最大分数,要么是不选择这一轮直接获得三轮前的最大分数。
之所以要区分
i >= 3
和i < 3
之间的区别,是因为题目告知了若当前轮次小于等于3
(即在第1
、2
、3
轮选择跳过轮次),则总分数置为0
。
代码就呼之欲出了,整体难度不高。
代码
Python
# 题目:【DP】2024E-玩牌高手
# 分值:100
# 作者:闭着眼睛学算法
# 算法:DP
# 代码看不懂的地方,请直接在群上提问
# 输入数组
nums = list(map(int, input().split(",")))
# 获得数组长度n
n = len(nums)
# 初始化长度为n的dp数组,初始化其中元素均为0
# dp[i]表示经过第i轮之后,可以获得的最大分数
dp = [0] * n
# 初始化dp[0],如果nums[0]大于0,则第0轮选择nums[0]的分数
# 设置dp[0]为nums[0],否则dp[0]保持为0即可
if nums[0] > 0:
dp[0] = nums[0]
# 从第1轮开始考虑所有轮次的选择
for i in range(1, n):
# 若属于前3轮,
# 若当前轮次选上,则分数为前一轮分数dp[i-1]加上当前轮次分数nums[i]
# 若当前轮次不选,则分数为0
# 取两者之间较大值
if i < 3:
dp[i] = max(dp[i-1]+nums[i], 0)
# 若不属于前3轮,
# 若当前轮次选上,则分数为前一轮分数dp[i-1]加上当前轮次分数nums[i]
# 若当前轮次不悬,则分数三轮前的分数dp[i-3]
# 取两者之间较大值
else:
dp[i] = max(dp[i-1]+nums[i], dp[i-3])
# 输出最后一轮能获得的最大分数
print(dp[-1])
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入数组,并处理输入
String[] input = scanner.nextLine().split(",");
int n = input.length;
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = Integer.parseInt(input[i]);
}
// 初始化长度为n的dp数组,初始化其中元素均为0
int[] dp = new int[n];
// 初始化dp[0],如果nums[0]大于0,则第0轮选择nums[0]的分数
// 设置dp[0]为nums[0],否则dp[0]保持为0即可
if (nums[0] > 0) {
dp[0] = nums[0];
}
// 从第1轮开始考虑所有轮次的选择
for (int i = 1; i < n; i++) {
if (i < 3) {
// 若属于前3轮
dp[i] = Math.max(dp[i - 1] + nums[i], 0);
} else {
// 若不属于前3轮
dp[i] = Math.max(dp[i - 1] + nums[i], dp[i - 3]);
}
}
// 输出最后一轮能获得的最大分数
System.out.println(dp[n - 1]);
}
}
C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// 输入数组并处理输入
string input;
getline(cin, input);
vector<int> nums;
string temp = "";
for (char ch : input) {
if (ch == ',') {
nums.push_back(stoi(temp));
temp = "";
} else {
temp += ch;
}
}
nums.push_back(stoi(temp));
int n = nums.size();
// 初始化长度为n的dp数组,初始化其中元素均为0
vector<int> dp(n, 0);
// 初始化dp[0],如果nums[0]大于0,则第0轮选择nums[0]的分数
if (nums[0] > 0) {
dp[0] = nums[0];
}
// 从第1轮开始考虑所有轮次的选择
for (int i = 1; i < n; i++) {
if (i < 3) {
// 若属于前3轮
dp[i] = max(dp[i - 1] + nums[i], 0);
} else {
// 若不属于前3轮
dp[i] = max(dp[i - 1] + nums[i], dp[i - 3]);
}
}
// 输出最后一轮能获得的最大分数
cout << dp[n - 1] << endl;
return 0;
}
C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
// 输入数组并处理输入
char input[1000];
fgets(input, sizeof(input), stdin);
int nums[1000]; // 假设数组长度不会超过1000
int n = 0; // 记录数组的实际大小
char* token = strtok(input, ",");
while (token != NULL) {
nums[n++] = atoi(token); // 将字符串转换为整数并存入数组
token = strtok(NULL, ",");
}
// 初始化长度为n的dp数组,初始化其中元素均为0
int dp[1000] = {0}; // 假设dp数组大小不超过1000
// 初始化dp[0],如果nums[0]大于0,则第0轮选择nums[0]的分数
if (nums[0] > 0) {
dp[0] = nums[0];
}
// 从第1轮开始考虑所有轮次的选择
for (int i = 1; i < n; i++) {
if (i < 3) {
// 若属于前3轮
dp[i] = (dp[i - 1] + nums[i] > 0) ? dp[i - 1] + nums[i] : 0;
} else {
// 若不属于前3轮
dp[i] = (dp[i - 1] + nums[i] > dp[i - 3]) ? dp[i - 1] + nums[i] : dp[i - 3];
}
}
// 输出最后一轮能获得的最大分数
printf("%d\n", dp[n - 1]);
return 0;
}
JavaScript
// 读取输入并处理输入
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('', (input) => {
const nums = input.split(",").map(Number); // 将输入字符串转换为数字数组
const n = nums.length;
// 初始化长度为 n 的 dp 数组,初始化其中元素均为 0
const dp = new Array(n).fill(0);
// 初始化 dp[0],如果 nums[0] 大于 0,则第 0 轮选择 nums[0] 的分数
if (nums[0] > 0) {
dp[0] = nums[0];
}
// 从第 1 轮开始考虑所有轮次的选择
for (let i = 1; i < n; i++) {
if (i < 3) {
// 若属于前 3 轮
dp[i] = Math.max(dp[i - 1] + nums[i], 0);
} else {
// 若不属于前 3 轮
dp[i] = Math.max(dp[i - 1] + nums[i], dp[i - 3]);
}
}
// 输出最后一轮能获得的最大分数
console.log(dp[n - 1]);
rl.close();
});
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
// 创建读取器
reader := bufio.NewReader(os.Stdin)
// 读取输入
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
strArr := strings.Split(input, ",")
// 将输入字符串数组转换为整数数组
var nums []int
for _, str := range strArr {
num, _ := strconv.Atoi(str)
nums = append(nums, num)
}
n := len(nums)
// 初始化长度为 n 的 dp 数组,初始化其中元素均为 0
dp := make([]int, n)
// 初始化 dp[0],如果 nums[0] 大于 0,则第 0 轮选择 nums[0] 的分数
if nums[0] > 0 {
dp[0] = nums[0]
}
// 从第 1 轮开始考虑所有轮次的选择
for i := 1; i < n; i++ {
if i < 3 {
// 若属于前 3 轮
dp[i] = max(dp[i-1]+nums[i], 0)
} else {
// 若不属于前 3 轮
dp[i] = max(dp[i-1]+nums[i], dp[i-3])
}
}
// 输出最后一轮能获得的最大分数
fmt.Println(dp[n-1])
}
// 辅助函数,返回两个整数的较大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
时空复杂度
时间复杂度:O(N)
。仅需一次遍历数组。
空间复杂度:O(N)
。dp
数组所占空间。