Loading

2025dsfz集训Day12: 斜率优化DP

Day12:斜率优化DP

一次函数与斜率

  • 斜率:表示一个直线倾斜程度。定义为和正方向水平轴的夹角的正切值。
  • 经过两个点 \((x1, y1)\)\((x2, y2)\) 的直线的斜率为 \(\frac{y2−y1}{x2−x1}\)
    (保证这两点不重合)
  • 可看出过两个竖直的点的直线斜率无定义。需特别关注。
    证明:
    \(x1,x2,y1,y2\) 带入同一个函数解释式得:

\[\left\{\begin{matrix}y_1=x_1k+d\\y_2=x_2k+d\end{matrix}\right. \]

其中,\(k\)为斜率,\(d\)为节距.
将两式相减可得:

\[(y_2-y_1)=k(x_2-x_1) \]

移项可得:

\[k=\frac{y_2-y_1}{x_2-x_1} \]

得证.

例题1:[一本通]打印文章

  • 输入长度为 \(n\) 的非负序列,输入权值 \(M\),把序列划分为若干连续段,每段的价值为这段权值和的平方加 \(M\),求一种划分方式,使得每段价值之和最小。

    • \(n ≤ 500000\)
  • \(dp_i\) 表示前 \(i\) 个数的最佳答案。考虑枚举倒数第二段的右端点 \(j\),写出转移式为:

\[dp_=min_{j=0}^{i-1}\{dp_j+sum[j+1:i]^2+M\} \]

  • 尝试优化,令 si 表示前缀和,则转移式为:

\[dp_i=M+min_{j-0}^{i-1}\{dp_j+(s_i-s_j)^2\} \]

\[dp_i=M+s_i^2+min_{j=0}^{i-1}\{dp_j+s_j^2-2s_is_j\} \]

  • \(2s_is_j\) 让含 \(j\) 项和含 \(i\) 项无法分开,无法直接数据结构优化。
  • \(res_j = M + s_i^2 + dp_j + s_j^2 − 2s_is_j\),表示从 j 转移到 i 的值。显然,\(dp_i=min_{j=0}^{i-1}res_j\)

\[dp_j + s_j^2 = 2s_is_j + (res_j − M − s_i^2) \]

  • 若把 \(P_j(s_j, dp_j + s_j^2)\) 看作二维平面上的一个点?
  • 则直线 \(y = (2s_i)x + (res_j − M − s_i^2)\) 一定过 \(P_j\)。且此直线斜率固定为 \(2s_i\),纵截距为 \(res_j − M − s_i^2\)
  • 对于固定的 \(i\),求 \(res_j\) 中的最小值,转化为:给定若干个 \(P_j\),求过每个点斜率为 \(2s_i\) 的直线的截距最小值。

下凸壳

直观理解: 只有所有的 \(P_j\) 里“最靠下”的点有可能成为答案。如何定义“最靠下”?

  • 我们定义“不靠下”的点:
    • 点集中两个点 \(A(x_A, y_A), B(x_B, y_B)\),若满足 \(x_A = x_B\),且 \(y_A < y_B\),则 \(B\) 是“不靠下”的点。
    • 点集中三个点 \(A(x_A, y_A), B(x_B, y_B), C(x_C, y_C)\),若满足 \(x_A < x_B < x_C\),且 \(k_{AB} > k_{BC}\),则 \(B\) 是“不靠下”的点。
  • 称剩余不是“不靠下”的点形成的点集为:下凸壳。
  • 只有下凸壳上的点可能成为最优转移点 \(res_j\)
  • 证明:一个“不靠下”的点,第一种情况显然不可能。
  • 第二种情况,即这个电是 \(P_A, P_B, P_C\),其中 \(s_A < s_B < s_C\),且 \(k_{AB} > k_{BC}\) 中的 \(P_B\) 点。直接放缩可证明 \(k_{AB} > k_{AC} > k_{BC}\)(三弦引理)。
  • 先考虑两个转移点 \(P, Q\)
    • \(2s_i < k_{PQ}\) 时,\(P\)\(Q\) 优。
    • \(2s_i ≥ k_{PQ}\) 时,\(Q\)\(P\) 优。
    • \(2s_i < k_{AC} < k_{AB}\) 时,\(A\)\(B\) 优。
    • \(2s_i ≥ k_{AC} > k_{BC}\) 时,\(C\)\(B\) 优。
  • 所以无论何时,\(B\) 一定不是最优转移点。

实现方式

  • 实现方式:用单调队列维护下凸壳。每在右边(\(s_i\) 单调不降)加一个新点,按照斜率单调递增的原则维护单调队列。每次求 \(dp_i\) 时,在左侧弹掉斜率小于 \(2s_i\) 的点,则队头为切点。时间复杂度为 \(O(n)\).
  • 转移式为 \(1D1D\) 形式,形如 \(dp_i = min_j\{F(i) + G(j) + H(i) × R(j)\}:\)
  • 转化为 \(G(j) = (−H(i)) × R(j) + (resj − F(i))\)
  • 再转化为,二维平面上有若干点 \(P_j(R(j), G(j))\),用斜率为 \(−H(i)\) 的直线去过这些点。最小(大)化纵截距,
  • 维护下(上)凸壳解决。

上一道题满足了 \(R\) 函数是单调不降函数,这让我们可以直接用队列维护下凸壳。而且满足了 \((-H)\) 函数是单调不降函数,这让我们可以用单调队列直接把队头弹掉。

[一本通] Cats Transport

\(S\) 是农场主,他养了 \(M\) 只猫,雇了 \(P\) 位饲养员。农场中有一条笔直的路,路边有 \(N\) 座山,从 \(1\)\(N\) 编号。第 \(i\) 座山与第 \(i − 1\) 座山之间的距离是 \(D_i\)。饲养员都住在 \(1\) 号山上。
有一天,猫出去玩。第 \(i\) 只猫去 \(H_i\) 号山玩,玩到时刻 \(T_i\) 停止,然后在原地等饲养员来接。饲养员们必须回收所有的猫。每个饲养员沿着路从 \(1\) 号山走到 \(N\) 号山,把各座山上已经在等待的猫全部接走。饲养员在路上行走需要时间,速度为 \(1\) 米每单位时间。饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。
例如有两座相距为 \(1\) 的山,一只猫在 \(2\) 号山玩,玩到时刻 \(3\) 开始等待。如果饲养员从 \(1\) 号山在时刻 \(2\)\(3\) 出发,那么他可以接到猫,猫的等待时间为 \(0\)\(1\)。而如果他于时刻 \(1\) 出发,那么他将于时刻 \(2\) 经过 \(2\) 号山,不能接到当时仍在玩的猫。
你的任务是规划每个饲养员从 \(1\) 号山出发的时间,使得所有猫等待时间的总和尽量小。饲养员出发的时间可以为负。
\(N, M ≤ 10^5, P ≤ 100, Di ≤ 10^4, 1 ≤ H_i ≤ N, T_i ≤ 10^9\)

  • \(sd_i\)\(i\) 号山到 \(1\) 号山的距离。
    • 某饲养员出发时间为 \(s\),则他能接到的猫需要满足 \(s + sd_{H_i} ≥ T_i\),等待时间为:

\[s + sd_{H_i} − T_i。 \]

  • 那么令 \(a_i = T_i − sd_{H_i}\),问题转化为在数轴上放置 \(P\) 个存档点,最小化每个 \(a_i\) 和右侧最近的存档点距离之和。
  • 很明显所有饲养员只会在某个 \(ai\) 时刻出发,则问题转化为离散问题。
  • \(a_i\) 从小到大排序。\(dp_{i,j}\) 表示前 \(i\) 个猫,用 \(j\) 个饲养员接,等待时间之和最小值。
  • 转移为:(si 为 ai 的前缀和)

\[dp_{i,j}=min^{i-1}_{k=0}dp_{k,j−1} + a_i(i − k) − (s_i − s_k) \]

  • 第二维可滚动,套用斜率优化一般形式,化为:

\[dp_{k,j−1} + s_k = a_i\times k + (res_k − a_i\times i + s_i) \]

  • 平面上的点为 \(P_k (k, dp_{k,j−1} + s_k )\),斜率为 \(a_i\)
  • 横坐标单调递增,斜率单调递增,可直接单调队列维护。时间复杂度 \(O(NP)\)

[一本通]任务安排

\(N\) 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。机器会把这 \(N\) 个任务分成若干批,每一批包含连续的若干个任务。
从时刻 \(0\) 开始,任务被分批加工,执行第 \(i\) 个任务所需的时间是 \(T_i\)。另外,在每批任务开始前,机器需要 \(S\) 的启动时间,故执行一批任务所需的时间是启动时间 \(S\) 加上每个任务所需时间之和。
一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。也就是说,同一批任务将在同一时刻完成。
每个任务的费用是它的完成时刻乘以一个费用系数 \(C_i\)。请为机器规划一个分组方案,使得总费用最小。
\(n ≤ 300000, 1 ≤ S ≤ 256, |Ti| ≤ 256, 0 ≤ Ci ≤ 256\)

  • 费用需要差分计算,将每个任务的费用拆到每组任务的花费时间上
  • \(dp_i\) 表示前 \(i\) 个任务做完后,对总费用的贡献最小值。
  • \(st\)\(T\) 的前缀和,\(sc\)\(c\) 的前缀和。枚举当前上一批任务的右端点 \(j\),有:

\[dp_i =min_{j=0}^{i-1}{dp_j + (sc_n − sc_j)(S + st_i − st_j)} \]

  • 按照惯例,整理为:

\[dp_j − sc_n × st_j − sc_j × S + sc_j × st_j = st_i × sc_j + (res_j − sc_n × S − sc_n × st_i) \]

  • 二维平面上的点为 \(P_j(sc_j, dp_j − sc_n × st_j − sc_j × S + sc_j × st_j)\),查询斜率为 \(st_i\)
  • 横坐标依然单调递增,可直接维护下凸壳。但是查询斜率不递增了,不能用单调队列维护。
  • 用单调栈维护,查询时在单调栈上二分即可。时间复杂度 \(O(n log n)\)

NOI2007 货币兑换

有两种货物。每天这两种货物的市场价格都会改变,第 \(k\) 天,单价分别为 \(A_k\)\(B_k\)
有两种交易方式,一种是买,支付 \(P\) 元,获得数量比例为 \(r_k\) 的 A 货物和 B 货物。
另一种是卖,将当前手里占比为 \(p\)\(A\) 货物和 \(B\) 货物卖成现金。\(p ∈ [0, 1]\)
每天可以自由买卖,初始没有货物,有 \(S\) 元钱,求 \(N\) 天后最多多少现金。
\(N ≤ 10^5, 0 < Ak , Bk ≤ 10, 0 < rk ≤ 100\)

  • 必然存在一种最优的方案满足:每次买都花光所有现金,每次卖都卖光所有货物。
  • 那么在同一天内,一定是先卖光(或者不卖),再全买进(或者不买)。
  • 设 dpi 表示第 i 天卖光 (或者不卖) 后最多有多少钱。枚举上次是第 j 天买的,得:

\[dp_i = max(dp_{i−1},max_{j=0}^{i-1}{dp_j × (a_j × A_i + b_j × B_i)}) \]

  • 其中 \(a_j =\frac{r_j}{A_j×r_j+B_j}, b_j = \frac{1}{A_j×r_j+B_j}\) ,表示当天 \(1\) 元钱会买进 \(A\) 货物和 \(B\) 货物的数量。
  • 我们姑且忽略和 \(dp_{i−1}\)\(max\) 的那部分,按照惯例定义 \(res_j = dp_j × (a_j × A_i + b_j × B_i)\),推式子可得:

\[dp_j\times b_j = −\frac{A_i}{B_i}(dp_j\times a_j) + \frac{res_j}{B_i} \]

  • 此时二维平面上的点为 \(P_j(dp_j\times a_j, dp_j\times b_j)\),用斜率为 \(−\frac{A_i}{B_i}\) 的直线去过这些点,最大化纵截距。
  • 无论是点的横坐标还是斜率都不单调了。可以用李超线段树来做,但斜率优化也可以做。
  • 这个题就利用分治加斜率优化去做。直接做时间复杂度为 \(O(n log2 n)\)。但是分治本身结构就是归并排序的结构。利用归并排序,可将时间复杂度降为 \(O(n log n)\)

一般形式其他优化

  • 回顾形式 \(G(j) = (−H(i)) × R(j) + (res_j − F(i))\)。用斜率为 \(−H(i)\) 的直线过 \(P_j(R(j), G(j)))\)
  • 现在我们考虑,如果横坐标 \(R(j)\) 不单调递增,怎么维护凸壳呢?

序列上的斜率优化可考虑分治。

  • 对于一个分治区间 \([l, mid]\)\([mid + 1, r]\),我们考虑转移点在 \([l, mid]\) 范围内,对 \(i ∈ [mid + 1, r]\) 的所有 \(dp_i\) 的贡献。这样转移点内部就离线下来了,可以做排序,变成了横坐标单调的情况。对于每个 \(i ∈ [mid + 1, r]\),在固定的凸壳上做二分。
  • 先递归到左子区间,把 \([l, mid]\) 的所有 \(dp\) 最终值求出来。然后考虑 \([l, mid]\)\([mid + 1, r]\) 的转移。最后递归到 \([mid + 1, r]\),只需考虑内部转移。

相当于一个分治树上的中序遍历。

posted @ 2025-01-25 18:37  FrankWkd  阅读(31)  评论(0)    收藏  举报