受限于在线编辑器的性能瓶颈(省流:写太多了会很卡),在此决定把另外的(不在算法提高课中)DP斜率优化题目单独拎出来写一篇做题笔记。旨在记录算式消元换元的原则与技巧、凸包维护的方法。题目大致由易到难。
洛谷 P3195 [HNOI2008] 玩具装箱#
题目地址:P3195
题目难度:省选/NOI-
P 教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。
P 教授有编号为 1⋯n 的 n 件玩具,第 i 件玩具经过压缩后的一维长度为 Ci。
为了方便整理,P教授要求:
- 在一个一维容器中的玩具编号是连续的。
- 同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物。形式地说,如果将第 i 件玩具到第 j 个玩具放到一个容器中,那么容器的长度将为 x=j−i+k=i∑jCk。
制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为 x,其制作费用为 (x−L)2。其中 L 是一个常量。P 教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过 L。但他希望所有容器的总费用最小。
输入格式:
第一行有两个整数,用一个空格隔开,分别代表 n 和 L。
第 2 到 第 (n+1) 行,每行一个整数,第 (i+1) 行的整数代表第 i 件玩具的长度 Ci。
输出格式:
输出一行一个整数,代表所有容器的总费用最小是多少。
数据结构:
对于全部的测试点,1≤n≤5×104,1≤L≤107,1≤Ci≤107。
这道题就和《任务安排》一个模子了。设计状态为选前 i 个物品的花费最小值。同样枚举 i∈[1,n],j∈[0,i−1] 然后代入题面长度公式,我们就能得到 O(n2) 的暴力递推公式:dp[i] = min(dp[i], dp[j] + (i - j - 1 + sumC[i] - sumC[j] - L) * (i - j - 1 + sumC[i] - sumC[j] - L))。
对等式进行化简:
dpi=dpj+(i−j−1+si−sj−L)2Define: sk=i=1∑kci考虑到 L 为常数,换元 L+1=L′,因此:
dpi=dpj+(i+si−j−sj−L′)2发现一组同构,令 ak=k+sk,再次化简为:
dpi=dpj+(ai−aj−L′)2然后拆括号:
dpi=dpj+ai2+aj2+L′2−2aiaj−2aiL′+2ajL′我们的斜率需要一个只和循环变量 i(以及常数项) 有关的值,对于直线方程,我们不希望斜率里出现任何有关不定项 j 的元素。综上,我们移项得到如下:
dpj+aj2=2(ai−L′)aj+dpi−ai2+2aiL′−L′2此时直线方程的每个要素都很明了了。因为 ci 为正,就有 si 单调递增。根据上式有 dpi=b+ai2−2aiL′+L′2=b+(ai−L′)2,不单调递减,启示我们维护截距 b 的最小值。因此可以直接单调队列维护下凸壳。具体要求如下:
- 创建一个双端队列来存放凸包上的点的下标
- 如果队头元素满足 ahh+1−ahh(dphh+1+ahh+12)−(dphh+ahh2)≤2(ai−L′),则弹出队头。因为队头所维护的点的斜率已经失去最优性,在单调递增的斜率意义下不会再被用到。
- 如果队尾元素满足 att−att−1(dptt+att2)−(dptt−1+att−12)≥ai−att(dpi+ai2)−(dptt+att2),则弹出队尾。因为引入的新点 i 使得原先维护的最值点不再最值,类比单调队列中不符合单调性时的弹出操作。
为了避免如上除法操作带来精度影响,故采用交叉相乘法。此时就需要注意相乘结果是否会爆类型。
代码在此
推式子时居然把斜率弄成了 ΔkΔy???
洛谷 P3628 [APIO2010] 特别行动队#
题目地址:P3628
题目难度:省选/NOI-
题目来源:APIO 2010
你有一支由 n 名预备役士兵组成的部队,士兵从 1 到 n 编号,你要将他们拆分成若干特别行动队调入战场。出于默契的考虑,同一支特别行动队中队员的编号应该连续,即为形如 (i,i+1,⋯i+k)的序列。所有的队员都应该属于且仅属于一支特别行动队。
编号为 i 的士兵的初始战斗力为 xi ,一支特别行动队的初始战斗力 X 为队内士兵初始战斗力之和,即 X=xi+xi+1+⋯+xi+k。
通过长期的观察,你总结出对于一支初始战斗力为 X 的特别行动队,其修正战斗力 X′=aX2+bX+c,其中 a, b, c 是已知的系数(a<0)。 作为部队统帅,现在你要为这支部队进行编队,使得所有特别行动队的修正战斗力之和最大。试求出这个最大和。
输入格式:
输入的第一行是一个整数 n,代表士兵的人数。
输入的第二行有三个用空格隔开的整数,依次代表 a, b, c,即修正战斗力的系数。
输入的第三行有 n 个用空格隔开的整数,第 i 个整数代表编号为 i 的士兵的初始战斗力 xi。
输出格式:
输出一行一个整数,代表最大的所有特别行动队战斗力之和。
数据范围:
对于 20% 的数据,n≤103。
对于 50% 的数据,n≤104
对于 100% 的数据,1≤n≤106,−5≤a≤−1,−107≤b≤107,−107≤c≤107,1≤xi≤100。
写出暴力状态转移方程:dp[i] = max(dp[i], dp[j] + a * X * X + b * X + c),其中 X=si−sj,sk=i=1∑kxi。能拿 50 分,考虑优化。
dpi2asisj+dpi−asi2−bsi=dpj+asi2+asj2−2asisj+bsi−bsj+c=dpj+asi2+bsi+asj2−bsj−2asisj+c=dpj+asj2−bsj那么斜率就是 2asi、y 就是 dpj+asj2−bsj、截距 t=dpi−asi2−bsi。我们需要最大化 dpi,观察到 dpi=t+asi2+bsi,我们需要让截距最大化。
类比先前推导过的最小化截距的方法,我们可以轻松得出一个结论——截距最大化,维护上凸壳!自己画个图可以知道,我们需要找的最值点就是第一个斜率小于当前直线斜率的点。观察到 2asi 有单调性,因此使用单调队列维护。因为这里的 a 是负数,所以在判断出队时需要变号。
代码在此