JS1k: Breathing Galaxies (1013 bytes)

Day10 dp 优化(四边形不等式、斜率优化、决策 单调性、CDQ 分治、数据结构)

动态规划优化方法

概述

本部分讲解单调队列优化DP、斜率DP、决策单调性优化DP。我们以1D/1D类DP为例进行说明。

问题形式

给定二元函数\(val(i, j)\),我们需要对形如:

\[f_j = \min_{i=0}^{j-1} f_i + val(i, j) \]

的DP转移进行快速计算。

问题分类

  1. 离线问题:计算\(f_j = \min_{i=0}^{j-1} g_i + val(i, j)\),其中g是已知序列
  2. 半在线问题:转移为\(f_j = \min_{i=0}^{j-1} f_i + val(i, j)\),但\(val(i,j)\)与f无关

大多数情况下遇到的是半在线问题。

转移限制

可能存在转移限制函数\(C(i,j)\in\{0,1\}\),表示能否从i转移到j。

优化方法本质

这些优化方法本质上都是通过维护决策集合,分析问题性质,去除无用决策,从而简化维护过程。


例题

P1886 滑动窗口 /【模板】单调队列

题目链接Luogu P1886

问题描述
给定长度为n的序列a和定值k。对所有长度为k的子段,求该子段中a的最大值。

数据范围\(1 \leq k \leq n \leq 10^6\)

解题思路
模板-单调队列。“当一个人比你小,还比你强,你就打不过它了”

我们从 \(1\)\(n\) 枚举 \(i\), 维护一个双端队列 \(Q\), \(Q\) 记录的是:还有可能成为某个窗口的最优值的数构成的集合。

归纳说明:\(Q\) 中的元素从左往右是单调不升的。

加入 \(i\) 这个决策时,队列末尾 \(\leq a_i\) 的元素对后面的答案没有影响了,弹出它们,然后加入 \(i\)

怎么处理长度为 \(k\) 的限制:弹出队首 \(\leq i-k\) 的元素。显然这两个操作都使 \(Q\) 仍然单调。

查询:查队首即可。


区间划分问题

问题描述
给定两个长度为n的序列a,b。将\(\{1,2,...,n\}\)划分为若干非空子段,要求:

  • 每段长度\(\leq k\)
  • \([l,r]\)的权值为\(a_l + b_r\)
    求最大权值和。

数据范围\(1 \leq k \leq n \leq 10^6\)

解题思路
\(f_{i}\) 是将 \([1,i]\) 划分成若干段得到的最优值,转移即:

\(f_{i}=\max_{j=i-k}^{i-1}(f_{j}+a_{j+1}+b_{i})\)

考虑怎么优化:我们化一下式子,令 \(v_{j}=f_{j}+a_{j+1}\) ,转移就变成

\(f_{i}=b_{i}+\max_{j=i-k}^{i-1}v_{j}\)

发现就是对 \(v\) 做滑动窗口。一个道理。

总结一下:如果 \(val(i,j)\) 能拆成:一个只与 \(i\) 有关的量,加上一个只与 \(j\) 有关的量。

并且随着 \(j\) 变化,能取的 \(i\) 构成滑动窗口。就能单调队列做。。


P2365 任务安排

题目链接Luogu P2365

问题描述
\(n\) 个任务排成一个序列在一 台机器上等待完成(顺序不得改 变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时 间为 \(t_{i}\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_{i}\)。请确定一 个分组方案,使得总费用最小。

数据范围\(n \leq 10^{7}\)\(0 \leq s \leq 50\)\(0 \leq t_{i}\)\(f_{i} \leq 100\)

解题思路
先想一下怎么 \(O(n^{2})\) 怎么做。 如果 一次加工取了区间 \([i,j]\) , 那消耗的时间就是 \(s + t_{i} + t_{i+1} + \cdots + t_{j}\)

类似关路灯那个 题, 可以提前计算 \(j+1\) 及后面的 任务带来的费用。 也就是 说:我们 令 \(val(i,j) = (s + t_{i} + t_{i+1} + \cdots + t_{j})(f_{i} + f_{i+1} + \cdots + f_{n})\)

一组 划分方案的权值确为每 一段 的 \(val\) 的和。

\(t, f\) 做 前 缀和,设得到了 \(a, b\) 两个 数组,那么 \(val(l, r) = (s + a_{r} - a_{l-1})(b_{n} - b_{l-1})\)

转移即 \(dp_{l} + val(l+1, r) \rightarrow dp_{r}\) 。 我们 把 \(val\) 改成左开右闭, 即 \(val(l, r) = (s + a_{r} - a_{l})(b_{n} - b_{l})\) , 这样方便一些。

思考怎么优化,能不能把 \(val(i, j)\) 拆成:只和 \(i\) 相关的量,加上 只和 \(j\) 相关的量?

不可以。 \(val(l, r) = (s - a_{l}) (b_{n} - b_{l}) + a_{r} b_{n} - a_{r} b_{l}\) 。发现 \(a_{r} b_{l}\) 是 拆不掉的。

考虑斜率优化。一般而言,斜率优化是说, \(val(l, r)\) 可以看成 \(c_{l} + d_{r} + p_{r} k_{l}\) 的形式,并且 \(x, k\) 具有一定的单调性。
在这个题中, \(p\) 单调不增, \(k\) 单调不降。

现在就来看一下怎样的决策可以被淘汰掉。我们把一个决策抽象成平面上的一个点 $ (k_l, c_l) $ 。

在这个想求最小值的问题里,我们可以证明:如果有三个点 $ A \(,\) B \(,\) C $ ,满足 $ x_A \leq x_B \leq x_C $ ,且 $ B $ 在线段 $ AC $ 上方,那么 $ B $ 就可以被淘汰掉。

为什么呢?简单来说,每次找最佳决策,相当于有一根斜率为 $ -p_r $ 的直线,从下往上切决策构成的点集,遇到的第一个点就是最佳决策。那肯定不会切到 $ B $ 啦。

我们知道 $ B $ 在 $ AC $ 上方,则 $ B $ 被淘汰了。

这说明我们只需要维护点集的下凸壳。

现在思考,加入一个决策后怎么继续维护凸壳。其实只需要维护一个栈,加入一个点后观察栈最后两个数和加入的点的位置关系,如果能 \(淘汰\) 栈顶就将其弹掉。不断弹,然后再把当前决策加进来。

怎么做查询?现在 \(p\) 是单调不增的,所以在点集不变的情况下,随着时间推移,最优决策的位置也单调不降(且是连续的)。那可以直接用一个指针,记录这个最优位置。

复杂度线性。

问: 如果 \(p\) 没有单调性怎么办?答:在凸包上二分 。\((\)参见 P5785\()\)
问? 如果 \(p\), \(x\) 都没有单调性怎么办?答:李超线段树 ;也可以 cdq 做 ,但可能会多个 \(\log\)


决策单调性优化 dp

单调队列优化,斜率优化,它们能 work 都是因为 \(val\) 在数值上可以拆成简单的形式。

有时候 \(val\) 非常混乱,比如说它可能是形如 \((j - i)^{5}\) 之类的混乱形式。

但比方说,我们在做 \(dp_{j} = \min dp_{i} + val(i, j)\) ,如果对于 \(i < i'\)\(val(i, j) - val(i', j)\)\(j\) 增大的过程中单调不降的,那设 \(p_{j}\)\(j\) 选取的决策,我们一定有 \(p\) 单调不降。

满足这个性质的 \(val\) 被认为是满足四边形不等式的。即对于任意 \(a \le b \le c \le d\) ,都有 \(val(a, d) + val(b, c) \ge val(a, c) + val(b, d)\)

首先我们有一个问题 : 为什么不能像斜率优化那样,用一个指针记录最优点 ,随着 \(j\) 变化移动指针即可?

原因:在斜率优化中,被保留的决策的 \(d p _ { i } + v a l ( i , j )\) 形成了单峰序列 。所以能直接根据相邻两项的大小关系调整指针。( 也是基于这个单峰的性质 ,才能在 \(p\) 不单调的时候做二分)

但这个题中只是决策单调,并没有上述性质 。

做法

法一:分治。dfs(\(l\), \(r\), \(L\), \(R\)) 表示:我们已经知道 \(p l\le p_l\) , \(p_r\le PR\) ,现在要求出 \(p_l\), \(p_{l+1}\), \(\cdots p_r\)

只需要取 \(l\), \(r\) 中点 \(mid\) ,暴力算 \(p_{mid}\) 然后递归至 dfs(\(l\), \(mid-1\), \(L\), \(p_{mid}\)), dfs(\(mid+1\), \(r\), \(p_{mid}\), \(R\)) 即可。

好处:一些问题中,直接算 val(\(l\), \(r\)) 是低效的,但是能高效的算 val(\(l\), \(r+1\)) \(-\) val(\(l\), \(r\)), val(\(l-1\), \(r\)) \(-\) val(\(l\), \(r\)) 。此时这个方法最有用。

坏处:你怎么求 \(p_{mid}\) ?是不是要把 \(dp_0\), \(dp_1\), \(\cdots\), \(dp_{mid-1}\) 算出来?所以在线的 dp 都不能用这个方法,只能做离线的事情。(也有一个中和的方法能做在线:分治套分治)

法二:维持一个序列 \(P\) 。 当我们算出来 \(dp_0\), \(dp_1\), \(\dots\), \(dp_i\) 后 , 设 \(P_j\) 表示 \([0,i]\) 中对 \(j\) 而言的最佳决策。

考虑算出 \(dp_i\) 后怎么更新序列 \(P\) 。可以证明 \(val\) 满足四边形不等式时 ,序列 \(P\) 也是单调不降的。所以一定是把 \(P\) 的一个后缀替换成 \(i\)

维护一个双端队列 , 维护 \(P\) 的连续段 。 加入 \(dp_i\) 后 , 不停尝试把栈顶整个连续段都弹掉(设这个连续段是 \((l,r,i')\) ,那也就是看在 \(j=l\) 的时候 \(i'\)\(i\) 谁更优)

如果没法把整个段弹掉,就开始二分 , 找到这段第 一个能被 \(i\) 替换的位置 。 最后在栈中加入新的连续段 , 表示 \(i\) 占领的位置 。

坏处:第一,只有 \(val\) 满足四边形不等式时 , 这个方法才 work, 如果是通过其他方法分析出的单调性 , 就不能使用 。 第二,你也不能像那个分治做法一样调整 \(val(l, r)\)

好处:可以做在线 。

posted @ 2025-07-26 10:44  AkevinMitnick  阅读(25)  评论(0)    收藏  举报