#P2782. 第3题-小苯的序列分割
-
1000ms
Tried: 25
Accepted: 5
Difficulty: 6
所属公司 :
阿里
时间 :2025年3月30日-阿里云(算法岗)
-
算法标签>动态规划
第3题-小苯的序列分割
题解
题面描述
给定一个长度为 n 的序列 a,要求将 a 恰好划分为 m 个非空的连续区间。对于每个区间求和得到一个新序列 b(长度恰好为 m),目标是最大化如下式子:
b1+b2×2+b3+b4×2+⋯
也就是说,b 中奇数位置的数字按原值相加,而偶数位置的数字需要乘以 2后再加。输入数据有多组,每组数据给出 n、m 以及序列 a。
思路
将问题转化为动态规划(DP)的求解。令 prefix[i] 为序列 a 的前 i 个数字之和。考虑状态 dp[i][j] 表示将前 i 个数字划分成 j 个段后的最优答案,其中第 j 段的权值取决于 j 是奇数还是偶数(权值分别为 1 或 2)。
设第 j 段的权值为 wj,则划分时如果最后一段区间为 [l+1,i],区间和为 prefix[i]−prefix[l],状态转移方程为:
dp[i][j] = maxl=j−1i−1 {dp[l][j−1] + wj×(prefix[i]−prefix[l]) }
化简后可以写成:
dp[i][j] = wj×prefix[i] + maxl=j−1i−1 {dp[l][j−1] - wj×prefix[l]}
这样,在固定 j 时,我们可以在遍历 i 时维护一个当前的最大值,优化状态转移的求解复杂度。
由于题目中所有测试数据中 n 的总和不超过 3000,使用 O(n×m) 的算法是可行的。
C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits>
using namespace std;
typedef long long ll;
const ll NEG_INF = -0x3f3f3f3f3f3f3f3fLL;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T--){
int n, m;
cin >> n >> m;
vector<ll> a(n+1), prefix(n+1, 0);
for(int i = 1; i <= n; i++){
cin >> a[i];
prefix[i] = prefix[i-1] + a[i]; // 计算前缀和
}
// dp[i][j] 表示将前 i 个数字划分成 j 段的最大答案
vector<vector<ll>> dp(n+1, vector<ll>(m+1, NEG_INF));
dp[0][0] = 0;
// 枚举段数 j,从 1 到 m
for(int j = 1; j <= m; j++){
// 权值:奇数段权值为 1,偶数段权值为 2
ll w = (j % 2 == 1 ? 1 : 2);
// max_val 用于维护 max_{l=j-1}^{i-1} (dp[l][j-1] - w * prefix[l])
ll max_val = NEG_INF;
// 枚举 i,从 j 到 n(保证至少有 j 个数字)
for(int i = j; i <= n; i++){
// 先更新 max_val,候选分界点 l = i-1
// 当 i == j 时,l 必须为 j-1
if(i-1 >= j-1){
max_val = max(max_val, dp[i-1][j-1] - w * prefix[i-1]);
}
// 状态转移:最后一段为 [l+1, i]
dp[i][j] = w * prefix[i] + max_val;
}
}
cout << dp[n][m] << "\n";
}
return 0;
}
Python
import sys
def main():
import sys
sys.setrecursionlimit(10000)
input = sys.stdin.readline
T = int(input())
for _ in range(T):
n, m = map(int, input().split())
a = [0] + list(map(int, input().split()))
prefix = [0]*(n+1)
# 计算前缀和
for i in range(1, n+1):
prefix[i] = prefix[i-1] + a[i]
NEG_INF = -10**18
# dp[i][j] 表示将前 i 个数字划分成 j 段的最大答案
dp = [[NEG_INF]*(m+1) for _ in range(n+1)]
dp[0][0] = 0
# 枚举段数 j 从 1 到 m
for j in range(1, m+1):
# 权值:奇数段权值为 1,偶数段为 2
w = 1 if j % 2 == 1 else 2
max_val = NEG_INF
# 枚举 i 从 j 到 n
for i in range(j, n+1):
# 更新 max_val,候选分界点 l = i-1
if i-1 >= j-1:
max_val = max(max_val, dp[i-1][j-1] - w * prefix[i-1])
# 状态转移
dp[i][j] = w * prefix[i] + max_val
print(dp[n][m])
if __name__ == "__main__":
main()
Java
import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
// 使用 BufferedReader 提高输入效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int T = Integer.parseInt(br.readLine().trim());
while(T-- > 0) {
String[] line = br.readLine().trim().split(" ");
int n = Integer.parseInt(line[0]);
int m = Integer.parseInt(line[1]);
String[] arr = br.readLine().trim().split(" ");
// 数组下标从 1 开始
long[] a = new long[n+1];
long[] prefix = new long[n+1];
for(int i = 1; i <= n; i++){
a[i] = Long.parseLong(arr[i-1]);
prefix[i] = prefix[i-1] + a[i]; // 计算前缀和
}
long NEG_INF = Long.MIN_VALUE / 2;
// dp[i][j] 表示将前 i 个数字划分成 j 段的最大答案
long[][] dp = new long[n+1][m+1];
for(int i = 0; i <= n; i++){
Arrays.fill(dp[i], NEG_INF);
}
dp[0][0] = 0;
// 枚举段数 j 从 1 到 m
for(int j = 1; j <= m; j++){
// 权值:奇数段权值为 1,偶数段为 2
long w = (j % 2 == 1 ? 1 : 2);
long maxVal = NEG_INF;
// 枚举 i 从 j 到 n(保证至少有 j 个数字)
for(int i = j; i <= n; i++){
// 更新 maxVal,候选分界点 l = i-1
if(i - 1 >= j - 1){
maxVal = Math.max(maxVal, dp[i-1][j-1] - w * prefix[i-1]);
}
// 状态转移
dp[i][j] = w * prefix[i] + maxVal;
}
}
System.out.println(dp[n][m]);
}
}
}
题目内容
小苯有一个长度为n的序列a,他希望你能将a划分为恰好m个非空的连续段,并将其中每一段中的数字求和,组成一个长度恰好m的新序列b。接着,最大化以下式子:
b1+b2×2+b3+b4×2...
即:b中奇数位置的数字之和,加上偶数位置的数字之和×2
请你帮他算算这个最大值是多少吧。
输入描述
本题有多组测试数据。
输入的第一行包含一个正整数T(1≤T≤100),表示数据组数。
接下来包含T组数据,每组数据的格式如下:
第一行两个正整数n,m(1≤m≤n≤3000),表示小苯的序列a的长度,以及需要恰好划分成的连续段个数。
第二行n个整数ai(−109≤ai≤109),表示序列a。
(保证同一个测试文件的所有测试数据中,n的总和不超过3000。)
输出描述
对于每组测试数据:
输出一行一个整数,表示所求式子的最大值。
样例1
输入
2
5 4
1 3 2 4 3
5 1
1 3 2 4 -3
输出
23
7
说明
对于第一组测试数据,划分为: [1,1],[2,2],[3,3],[4,5]这四个区间最优,b={1,3,2,7},最大和为:1+3×2+2+7×2=23