P7402 [COCI 2020/2021 #5] Sjeckanje 解题报告


P7402 [COCI 2020/2021 #5] Sjeckanje 解题报告

〇、写在前面

这篇解题报告旨在用最直观的方式,帮助你理解这道题从思索到解决的全过程。我们将从问题的核心难点出发,一步步将它转化为一个我们熟悉且可以解决的模型。

一、题目想让我们做什么?

首先,我们快速回顾一下题目要求:

  1. 有一个整数数组 a
  2. 要对它进行 q 次修改,每次都是给一个区间 [l, r] 里的所有数加上 x
  3. 每次修改后,都要回答一个问题:把当前的数组 a 切分成若干个连续的小段,每一段的价值是(这段的最大值 - 这段的最小值),我们希望所有小段的价值之和最大。求这个最大值。

举个例子: 数组是 a = [4, 3, 3, 4]
一种切分方法是 [4, 3], [3, 4]

  • 第一段 [4, 3] 的价值是 max(4, 3) - min(4, 3) = 4 - 3 = 1
  • 第二段 [3, 4] 的价值是 max(3, 4) - min(3, 4) = 4 - 3 = 1
  • 总价值是 1 + 1 = 2

我们需要找到一种切分方法,让这个总价值最大。

二、第一步:简化问题——引入“差分”

题面中的“区间加法”是一个非常经典的信号。处理这类问题,一个强大的工具是差分数组

我们定义一个差分数组 d,其中 d[i] = a[i] - a[i-1](我们让 d[1] = a[1],不过实际上 d[2]d[n] 才是我们关心的)。

为什么要用差分?
因为对原数组 a 的区间 [l, r] 加上 x,在差分数组 d 上只会影响两个点:

  • d[l] 会增加 x (因为 a[l] 增加了 x,而 a[l-1] 不变)。
  • d[r+1] 会减少 x (因为 a[r] 增加了 x,而 a[r+1] 不变,所以 a[r+1]-a[r] 减少了 x)。

这样,复杂的“区间修改”就变成了简单的“单点修改”,问题处理起来就方便多了。

三、第二步:找到价值的来源——差分与价值的关系

现在,我们的操作简化了,但目标(最大化所有分段的价值之和)和差分数组有什么关系呢?

我们来看一段单调的区间,比如 [2, 5, 8, 10]
它的价值是 10 - 2 = 8
我们看看它的差分值(只看相邻的):5-2=3, 8-5=3, 10-8=2。这些差分值都是正数。
它们的和是 3 + 3 + 2 = 8
这和区间的价值相等!

再看一个单调递减的区间 [10, 5, 2, 1]
它的价值是 10 - 1 = 9
它的差分值是:5-10=-5, 2-5=-3, 1-2=-1。这些都是负数。
它们绝对值的和是 |-5| + |-3| + |-1| = 5 + 3 + 1 = 9
这也和区间的价值相等!

核心发现:
对于一个单调的区间 [l, r],它的价值 max - min 就等于区间内所有相邻元素之差的绝对值之和,也就是 sum(|d[i]|) (i 从 l+1r)。

那如果区间不单调呢?比如 [2, 8, 5]

  • 它的价值是 8 - 2 = 6
  • 差分是 d_1 = 8-2 = 6d_2 = 5-8 = -3
  • |d_1| + |d_2| = 6 + 3 = 9
  • 6 < 9
    如果我们把它切分成单调的两段 [2, 8][5],总价值是 (8-2) + (5-5) = 6
    如果我们把它切分成 [2][8, 5], 总价值是 (2-2) + (8-5) = 3

这启发我们:为了让总价值最大,我们应该尽可能地把数组切分成一个个单调的小段。因为在单调小段内,价值可以完全由差分数组的绝对值累加得到。如果一个段不单调,它的价值会小于其内部差分绝对值的和,造成了“浪费”。

结论: 问题的目标,可以转化为最大化我们选择的 |d[i]| 之和。我们应该在差分值符号变化的地方(比如从正数变为负数)进行切分,这样每个小段内部的差分值符号都相同(或为0),保证了每个小段都是单调的。

所以,最终的总价值,似乎就是把所有差分值 d[i] (从 i=2 到 n) 的绝对值加起来?
等等,这也不完全对。比如 [2,0,2,1],差分是 [-2, 2, -1]|-2|+|2|+|-1| = 5。但最优切分是 [2,0,2], [1],价值是 (2-0) + (1-1) = 2

问题出在哪里?当我们因为 d[i]d[i+1] 符号不同而被迫切分时,我们损失了什么?
这个“损失”发生在 a[i] 这个点上。比如 a[i-1], a[i], a[i+1],如果 d[i]>0d[i+1]<0,说明 a[i] 是一个局部最高点。我们无法同时“上坡”又“下坡”。这意味着 d[i]d[i+1] 不能被算在同一个单调段的价值里。我们必须在它们之间做出选择。

这引出了我们的最终模型:一个在差分数组上的动态规划。

四、第三步:最终兵器——线段树动态规划

我们面对的是:

  1. 对差分数组 d 的单点修改。
  2. 每次修改后,查询一个全局的最优解。

“单点修改,区间(全局也是一种区间)查询” -> 线段树

线段树的每个节点,要维护其代表的差分区间 d[L...R] 的信息。我们需要什么信息,才能从子节点合并出父节点的信息呢?

这就是动态规划(DP)的部分了。对于一个区间 d[L...R],它的最优解可能受到区间外的影响。具体来说,d[L] 是否需要和左边的 d[L-1] 连接成一个单调段?d[R] 是否需要和右边的 d[R+1] 连接?

这提示我们,每个线段树节点 p 需要存储 4 个值:
sgt[p][左端点状态][右端点状态]

  • 左/右端点状态0 表示不与外部连接(即此处为切分点),1 表示需要与外部连接。
  • :在该状态下,这个区间能产生的最大价值。

如何合并(Updata 函数)?

假设我们要合并左孩子 lc(代表 d[L...mid])和右孩子 rc(代表 d[mid+1...R])来得到父节点 p 的信息。关键点在于 d[mid]d[mid+1] 的关系。

  1. 和谐情况:d[mid] * d[mid+1] >= 0
    它们的符号相同(或有0,0和任何符号都“和谐”)。这意味着从 a[mid-1]a[mid] 再到 a[mid+1] 的趋势是连贯的(一直是上坡或一直是下坡)。
    在这种情况下,把它们连接起来一定比不连接更优。因为连接起来可以把 |d[mid]||d[mid+1]| 的贡献都算上,而不连接则可能损失其中一个。
    所以,父区间的状态 [i][j] 就直接由子区间必须连接的状态 [i][1][1][j] 相加得到:
    sgt[p][i][j] = sgt[lc][i][1] + sgt[rc][1][j]

  2. 冲突情况:d[mid] * d[mid+1] < 0
    它们的符号相反。这意味着在 a[mid] 这里趋势发生了逆转(比如上坡后接着下坡)。d[mid]d[mid+1] 无法被包含在同一个单调段里。
    我们必须在这里“切一刀”。但这一刀有两种切法:

    • 保留 d[mid] 的贡献,放弃 d[mid+1](在连接处)。对应策略是:左半段要连接到 mid,右半段从 mid+1 重新开始。价值为 sgt[lc][i][1] + sgt[rc][0][j]
    • 放弃 d[mid] 的贡献,保留 d[mid+1]。对应策略是:左半段在 mid 处断开,右半段从 mid+1 连接过来。价值为 sgt[lc][i][0] + sgt[rc][1][j]
      我们取这两种策略中更好的那个:
      sgt[p][i][j] = max(sgt[lc][i][1] + sgt[rc][0][j], sgt[lc][i][0] + sgt[rc][1][j])

最终答案

每次修改完 d 数组上的两个点后,整个数组 a 的最大划分价值就是线段树根节点 rootsgt[root][1][1]
为什么是 [1][1]?因为对于整个 d[2...n] 区间,它的两端没有“外部”了,我们默认它们是连接的(即不作为切分点),这样能包含 d[2]d[n] 的贡献,肯定比不包含更优。

五、代码实现细节

  1. 数据范围n, q 很大,价值可能超过 int 范围,记得开 long long
  2. 差分数组:我们关心的是 d[2]d[n],所以线段树建在 [2, n] 这个区间上。
  3. 建树:可以在一开始 O(N) 建树,也可以像题解代码一样,在读入时,每次读入一个 a[i] 就计算 d[i],然后在线段树上做一次单点修改。后者代码更简洁,虽然理论上慢一点,但对于本题足够了。
  4. 修改
    • l, r, x 的修改,变成 d[l] += xd[r+1] -= x
    • 注意边界:如果 l=1d[l] 不在我们 [2,n] 的差分数组里,不用管。如果 r=nd[r+1] 超出范围了,也不用管。
    • 每次修改 d[k] 后,在线段树上更新位置 k 即可。

至此,我们就把一个复杂的问题,通过差分转化、价值分析、线段树DP,一步步地解决了。希望这份报告能帮助你彻底理解这道题的精髓!

posted @ 2025-07-16 11:05  surprise_ying  阅读(14)  评论(0)    收藏  举报