题解:学而思编程 数列分段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

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\),要求将其划分为若干段,使得每段的平均数都不超过 \(k\),求最多能划分成多少段。这是一个线性动态规划问题,关键在于将"平均数不超过 \(k\)"转化为前缀和的可比较形式,并通过枚举分割点进行状态转移。

  2. 算法选择

    • 前缀和预处理:快速计算任意区间 \([j+1, i]\) 的元素和
    • 线性 DPdp[i] 表示前 \(i\) 个元素最多能划分成的段数
    • 条件转化:将平均数约束 \(\frac{\sum_{t=j+1}^{i} a_t}{i-j} \leq k\) 转化为 \((sa_i - sa_j) \leq k \times (i - j)\),避免浮点运算
  3. 关键步骤

    • 初始化
      • 读取 \(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\)
    • 判断输出
      • dp[n] < 0:无法划分,输出 \(-1\)
      • 否则输出 dp[n]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n^2)\),枚举分割点 \(j\) 和终点 \(i\),共 \(O(n^2)\) 个状态
    • 空间复杂度:\(O(n)\),DP 数组 dp[0..n] 和前缀和数组 sa[0..n]
  5. 线性 DP 的核心思想

    • 状态设计dp[i] 表示前 \(i\) 个元素的最优解,具有明确的前缀结构,无后效性
    • 条件数学转化:将平均数约束转化为整数比较 \((sa_i - sa_j) \leq k \times (i - j)\),避免浮点数精度问题,同时保持等价性
    • 最优子结构:前 \(i\) 个元素的最优划分必然由前 \(j\) 个元素的最优划分加上最后一段 \([j+1, i]\) 构成
    • 不可达标记:用负无穷初始化表示状态不可达,dp[0] = 0 作为递推起点,最终通过 dp[n] 的符号判断是否有解
    • 适用于"序列划分 + 段约束"的最优化问题,枚举分割点是经典 DP 策略,当约束可转化为前缀和形式时尤为有效

【解题思路】

img/Pasted image 20260519124424.png

【算法标签】

线性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
posted @ 2026-07-04 13:21  团爸讲算法  阅读(6)  评论(0)    收藏  举报