题解:学而思编程 数列分段3
【题目来源】
学而思编程:数列分段3
【题目描述】
给出 \(n\) 项的数列,要求将数列划分成若干段,且每一段的平均数都不超过 \(k\)。求最多能划分成多少段?
【输入】
第 \(1\) 行,两个正整数 \(n,k\)
第 \(2\) 行,\(n\) 个正整数 \(a_1,a_2,\cdots,a_n\)
【输出】
一个整数,最多能划分成多少段。
如果不论如何划分也不能让每段平均数不超过 \(k\),输出 \(-1\)。
【输入样例】
7 7
9 9 4 3 9 7 4
【输出样例】
3
【核心思想】
-
问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\),要求将其划分为若干段,使得每段的平均数都不超过 \(k\),求最多能划分成多少段。这是一个线性动态规划问题,关键在于将"平均数不超过 \(k\)"转化为前缀和的可比较形式,并通过枚举分割点进行状态转移。
-
算法选择:
- 前缀和预处理:快速计算任意区间 \([j+1, i]\) 的元素和
- 线性 DP:
dp[i]表示前 \(i\) 个元素最多能划分成的段数 - 条件转化:将平均数约束 \(\frac{\sum_{t=j+1}^{i} a_t}{i-j} \leq k\) 转化为 \((sa_i - sa_j) \leq k \times (i - j)\),避免浮点运算
-
关键步骤:
- 初始化:
- 读取 \(n\)(数列长度)和 \(k\)(平均数上限)
- 读取数组 \(a[1..n]\),计算前缀和
sa[i] = sa[i-1] + a[i] memset(dp, -0x3f, sizeof(dp)):初始化为负无穷(不可达)dp[0] = 0:空序列划分成 \(0\) 段
- DP 转移(遍历 \(i\) 从 \(1\) 到 \(n\)):
- 遍历 \(j\) 从 \(0\) 到 \(i-1\)(枚举最后一段的起始前位置):
- 若 \((sa_i - sa_j) \leq k \times (i - j)\):
dp[i] = \max(dp[i], dp[j] + 1):前 \(j\) 个元素的最优段数加 \(1\)
- 若 \((sa_i - sa_j) \leq k \times (i - j)\):
- 遍历 \(j\) 从 \(0\) 到 \(i-1\)(枚举最后一段的起始前位置):
- 判断输出:
- 若
dp[n] < 0:无法划分,输出 \(-1\) - 否则输出
dp[n]
- 若
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2)\),枚举分割点 \(j\) 和终点 \(i\),共 \(O(n^2)\) 个状态
- 空间复杂度:\(O(n)\),DP 数组
dp[0..n]和前缀和数组sa[0..n]
-
线性 DP 的核心思想:
- 状态设计:
dp[i]表示前 \(i\) 个元素的最优解,具有明确的前缀结构,无后效性 - 条件数学转化:将平均数约束转化为整数比较 \((sa_i - sa_j) \leq k \times (i - j)\),避免浮点数精度问题,同时保持等价性
- 最优子结构:前 \(i\) 个元素的最优划分必然由前 \(j\) 个元素的最优划分加上最后一段 \([j+1, i]\) 构成
- 不可达标记:用负无穷初始化表示状态不可达,
dp[0] = 0作为递推起点,最终通过dp[n]的符号判断是否有解 - 适用于"序列划分 + 段约束"的最优化问题,枚举分割点是经典 DP 策略,当约束可转化为前缀和形式时尤为有效
- 状态设计:
【解题思路】

【算法标签】
线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int n, k;
int a[N], sa[N]; // a: 原数组, sa: 前缀和数组
int dp[N]; // dp[i]: 前i个元素最多能分成多少段
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sa[i] = sa[i - 1] + a[i]; // 计算前缀和
}
// 初始化dp数组为负无穷
memset(dp, -0x3f, sizeof(dp));
dp[0] = 0; // 空序列有0段
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
{
// 检查区间[j+1, i]是否满足条件
if ((sa[i] - sa[j]) <= k * (i - j))
{
// 如果可以分割,更新dp值
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
// 输出结果
if (dp[n] < 0)
{
cout << -1 << endl; // 无法分割
}
else
{
cout << dp[n] << endl; // 输出最大段数
}
return 0;
}
【运行结果】
7 7
9 9 4 3 9 7 4
3
浙公网安备 33010602011771号