题目链接
题目描述
给定一个长度为 的序列
和一个整数
。需要将序列
划分为恰好
个非空的连续段。将每段的和组成一个新的序列
。目标是最大化以下表达式的值:
即新序列中奇数位置的段和贡献1倍,偶数位置的段和贡献2倍。
思路分析
这是一个典型的序列分割型动态规划问题。
1. 状态定义
我们定义 为将原序列的前
个元素(
)划分为
段时,所能得到的最大目标值。
2. 状态转移
为了计算 ,我们需要考虑第
段(也就是最后一段)是从哪里开始的。假设第
段从索引
开始,一直到
结束。这意味着前
个元素被划分成了
段。
-
第
段的和为
。为了快速计算,我们可以预处理一个前缀和数组
,其中
。那么该段的和就是
。
-
第
段的权重
取决于
的奇偶性:如果
是奇数,
;如果
是偶数,
。
-
因此,
可以从所有可能的
转移而来,其中
。
状态转移方程为:
3. 复杂度与优化
直接实现上述转移方程,状态总数为 ,每次转移需要
的时间遍历
,总复杂度为
,会超时。
我们可以对转移方程进行变形:
对于固定的 和
,
是一个常数。我们需要最大化的是
这一部分。
当我们按 从小到大计算
时,
的取值范围也在不断扩大。我们可以维护一个变量
来记录
的前缀最大值。
优化后的 算法:
-
外层循环
从 1 到
(段数)。
-
内层循环
从
到
(元素数)。
-
在计算
这一“行”时,维护一个变量
。
4. 空间优化
注意到 的计算只依赖于
,我们可以使用滚动数组,将空间复杂度从
优化到
。
代码
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
using namespace std;
const long long INF = -1e18; // 使用一个足够小的数表示负无穷
void solve() {
int n, k;
cin >> n >> k;
vector<long long> s(n + 1, 0);
for (int i = 1; i <= n; ++i) {
long long val;
cin >> val;
s[i] = s[i - 1] + val;
}
vector<long long> dp(n + 1, INF);
vector<long long> prev_dp(n + 1, INF);
prev_dp[0] = 0; // 0个元素分成0段,和为0
for (int i = 1; i <= k; ++i) {
fill(dp.begin(), dp.end(), INF);
long long w = (i % 2 != 0) ? 1 : 2;
long long max_prev_term = INF;
for (int j = i; j <= n; ++j) {
// 更新 max_prev_term, p 的取值是 j-1
max_prev_term = max(max_prev_term, prev_dp[j - 1] - s[j - 1] * w);
if (max_prev_term > -1e17) { // 确保 max_prev_term 是有效的
dp[j] = s[j] * w + max_prev_term;
}
}
prev_dp = dp;
}
cout << dp[n] << endl;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
import java.util.Scanner;
import java.util.Arrays;
public class Main {
private static final long INF = -1_000_000_000_000_000_000L;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int t = sc.nextInt();
while (t-- > 0) {
solve(sc);
}
}
private static void solve(Scanner sc) {
int n = sc.nextInt();
int k = sc.nextInt();
long[] s = new long[n + 1];
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + sc.nextInt();
}
long[] prevDp = new long[n + 1];
Arrays.fill(prevDp, INF);
prevDp[0] = 0;
long[] dp = new long[n + 1];
for (int i = 1; i <= k; i++) {
Arrays.fill(dp, INF);
long w = (i % 2 != 0) ? 1 : 2;
long maxPrevTerm = INF;
for (int j = i; j <= n; j++) {
// 更新 maxPrevTerm
maxPrevTerm = Math.max(maxPrevTerm, prevDp[j - 1] - s[j - 1] * w);
if (maxPrevTerm > -1e17) {
dp[j] = s[j] * w + maxPrevTerm;
}
}
// 滚动数组
System.arraycopy(dp, 0, prevDp, 0, n + 1);
}
System.out.println(prevDp[n]);
}
}
import sys
def solve():
n, k = map(int, sys.stdin.readline().split())
a = list(map(int, sys.stdin.readline().split()))
s = [0] * (n + 1)
for i in range(n):
s[i+1] = s[i] + a[i]
INF = float('-inf')
prev_dp = [INF] * (n + 1)
prev_dp[0] = 0
for i in range(1, k + 1):
dp = [INF] * (n + 1)
w = 1 if i % 2 != 0 else 2
max_prev_term = INF
for j in range(i, n + 1):
# p = j - 1
max_prev_term = max(max_prev_term, prev_dp[j - 1] - s[j - 1] * w)
if max_prev_term != INF:
dp[j] = s[j] * w + max_prev_term
prev_dp = dp
print(prev_dp[n])
def main():
try:
t_str = sys.stdin.readline()
if not t_str: return
t = int(t_str)
for _ in range(t):
solve()
except (IOError, ValueError):
return
if __name__ == "__main__":
main()
算法及复杂度
- 算法:动态规划(带前缀和与状态转移优化)
- 时间复杂度:
。我们有两层主循环,分别遍历段数
和元素数
。
- 空间复杂度:
。通过使用滚动数组,我们将DP表所需的空间从
优化到了
。