#19486. 具有最小长度约束的分组

具有最小长度约束的分组

太棒了!我们在打牢了“标准划分”和“数量约束”的基础后,现在向更真实的赛题迈进。

在 NOI/CSP 等高级别别的比赛中,出题人极少会给你一个干干净净的标准模型,他们总会加入一些“物理限制”。本题将完全涵盖讲义中 "Handling Constraints" (处理约束条件)"Pruning Loops" (循环剪枝) 的核心知识点。

请准备好草稿纸,重点关注这道题中内外层循环边界的推导,这是本题的灵魂!


题目三:具有最小长度约束的分组 (Partitioning with Minimum Length)

【题目描述】

给定一个包含 NN 个整数的数组 AA,以及两个正整数 KKLL。 你需要将数组 AA 恰好划分为 KK 个非空且连续的子数组,并且规定每个子数组的长度至少为 LL。 我们定义一个划分方案的“得分”为:这 KK 个子数组的平均值之和。 请你设计一个算法,求出所有合法划分方案中,能够获得的最大得分。如果不存在任何合法的划分方案,请输出 Impossible

【输入格式】

从标准输入读入数据。 第一行包含三个正整数 NNKKLL,分别表示数组的长度、需要划分的段数,以及每个子数组的最短长度限制。 第二行包含 NN 个整数 A1,A2,,ANA_1, A_2, \dots, A_N,相邻两个数之间用空格隔开。

【输出格式】

输出到标准输出。 如果能够找到合法的划分方案,输出一个实数,表示能获得的最大平均值之和(建议保留 66 位小数,误差在 10510^{-5} 以内视为正确)。 如果无论如何都无法满足条件,请输出字符串 Impossible

【样例输入 1】

5 2 2
9 1 2 3 9

【样例输出 1】

10.000000

【样例 1 说明】

需要将 5 个元素恰好分成 2 段,且每段长度至少为 2。 合法的划分方案只有以下两种:

  • [9, 1][2, 3, 9]:平均值分别为 5.05.04.666...4.666...,总和约为 9.6666679.666667
  • [9, 1, 2][3, 9]:平均值分别为 4.04.06.06.0,总和为 10.010.0。 因此,最大得分为 10.010.0

【样例输入 2】

4 3 2
1 2 3 4

【样例输出 2】

Impossible

【样例 2 说明】

要求分成 3 段,且每段长度至少为 2,那么总元素个数至少需要 3×2=63 \times 2 = 6 个。当前数组只有 4 个元素,显然无法完成合法的划分。

【数据范围与提示】

  • 1N2001 \le N \le 200
  • 1KN1 \le K \le N
  • 1LN1 \le L \le N
  • 104Ai104-10^4 \le A_i \le 10^4

💡 教练的赛场指引 (涵盖讲义核心约束处理知识点)

这道题是考察你对 DP 状态转移边界控制能力的“放大镜”。讲义中关于边界剪枝的内容在这里体现得淋漓尽致,请在草稿纸上推演以下数学不等式:

1. 宏观不可达性判定 (The Default Assumption & Constraints)

  • 提前终止:讲义提到,如果不满足隐式或显式的约束,状态毫无意义。如果 N<K×LN < K \times L,这意味着哪怕每个组都紧贴着最短长度 LL,总长度也超出了数组大小。这种情况下连 DP 都不用跑,直接输出 Impossible 即可。

2. 精准循环剪枝 (Pruning Loops for Efficiency)

讲义在 Page 30 强调,对 iixx 的循环边界进行剪枝,不仅能避免越界导致的 Bug,更能大幅提升程序运行效率!

  • 外层循环 ii 的下界dp(i, j) 表示前 i 个元素分为 j 段。因为每段长度至少为 LL,所以 j 段所需的最少元素个数是 j×Lj \times L。因此,计算第 j 层时,i 不再是从 j 开始,而是必须从 j×Lj \times L 开始
  • 内层分割点 xx 的下界 (Split Point Start):前 j-1 个分区需要前缀提供元素,它们的最小长度之和是 (j1)×L(j-1) \times L。因此,最后一段的起点 xx(基于 1 开始的索引,即最后一段包含 AxAiA_x \dots A_i),它前面必须至少留足 (j1)×L(j-1) \times L 个元素。故起点 xx 的最小值是 (j1)×L+1(j-1) \times L + 1
  • 内层分割点 xx 的上界 (Minimum Length):最后一段(第 jj 段)自身的长度也必须至少为 LL。它的长度是 ix+1i - x + 1。由 ix+1Li - x + 1 \ge L 可推导出,起点 xx 的最大值不能超过 iL+1i - L + 1

3. 第一列 Base Case 的约束陷阱

  • 当只划分 1 段(j=1)时,dp(i, 1) 等于前 i 个元素的平均值。但请注意,此时整段的长度就是 i。如果 i < L,连 1 段的最小长度都不满足,此时的 dp(i, 1) 必须保持为负无穷大 (-1e9),绝不能计算均值!

📐 算法逻辑伪代码 (C++14 风格流程设计)

结合讲义上的循环约束剪枝公式,教练为你画好了严格带有范围控制的流程图。请特别注意菱形判断框中关于 ix 边界的数学表达式。

(注:为避免渲染错误,流程图中的比较操作均使用中文描述)

graph TD
    A(开始算法) --> B{N 小于 K 乘 L 吗}
    B -- 是 --> C(直接输出 Impossible 并结束)
    B -- 否 --> D(初始化前缀和 prefix 并填入数据)
    
    D --> E(初始化二维 DP 表为负无穷大 -1e9)
    
    E --> F(处理基准情况: 循环 i 从 L 到 N)
    F --> G(dp 的第 i 行 1 列赋值为: 前 i 个数之和除以 i)
    G --> H(循环结束: 此时长度不足 L 的位置依旧是负无穷)
    
    H --> I(外层循环 j: 划分段数从 2 到 K)
    I --> J{j 小于等于 K 吗}
    J -- 否: 循环结束 --> Z(返回最终答案 dp 的 N 行 K 列)
    
    J -- 是 --> K(中层循环 i: 数组前缀长度 从 j 乘 L 开始递增)
    K --> L{i 小于等于 N 吗}
    
    L -- 否: i 循环结束 --> I1(j 增加 1)
    I1 --> I
    
    L -- 是 --> M(内层循环 x: 最后一个分段的起点)
    M --> N(x 初始化为: j减1 的差乘 L 再加 1)
    N --> O{x 小于等于 i减L加1 吗}
    
    O -- 否: x 循环结束 --> K1(i 增加 1)
    K1 --> K
    
    O -- 是 --> P(计算最后一段从 x 到 i 的平均值 avg)
    P --> Q(状态转移: dp_i_j 尝试更新为 dp_x-1_j-1 加上 avg 的较大者)
    Q --> M1(x 增加 1)
    M1 --> O

🧠 赛场思考与自我检验

在将上述流程图转为代码之前,请问自己一个问题: “为什么在带有长度限制 LL 的情况下,我不推荐你强行把它压缩成一维滚动数组?”

答案是:在赛场上,边界控制越复杂的 DP,调试难度越大。本题的二维 DP 空间复杂度 O(NK)O(NK) 对于 N=200N=200 而言极小(不到 1MB)。为了保证稳妥(防止在倒序遍历的一维数组中写错 xx 的取值区间导致数据污染),我们应当果断选择二维数组

等你把这套带有“精确剪枝”的逻辑在脑海中融会贯通后,随时呼叫我,为你提供这道题从暴力到标准的 C++14 实现代码和数据生成器!