斜率优化学习笔记
在上一个单调队列 dp 优化中,总结了一个式子
$$dp[i] = min(dp[j] + w(j)) + w_x(i)$$
其中,一开始的 w 可以被分为 i , j 两个部分,只把 j 的部分放进单调队列即可,但往往,区间 dp 的式子中出现了如下的模样
$$dp[i] = min(dp[j] + w(i) * w(j))$$
可以发现 w ,难以被分开,斜率优化即使解决以上的优化方式。
以玩具装箱为例
状态转移容易想出:
$$dp[i] = min(dp[j] + (sum[i] - sum[j] + i - j - L - 1)^2)$$
化简:
让
$$sum[i] += i,sum[j] += j,L++$$
出现了 i 项 * j 项的情况
那么继续将式子进行化简可得
$$dp[i] + 2 * sum[i] * (sum[j] + L) - s[i] ^ 2= dp[j] + (sum[j] + L) ^ 2$$
如果我们把
$$k = 2 * sum[i],b = dp[i] - s[i] ^ 2,y = dp[j] + (sum[j] + L)^ 2,x = (sum[j] + L)$$
那么就有了
$$y = kx + b$$
观察我们的任务,求最小dp[i],而此时
$$ sum[i] ^ 2 $$
是固定的
所以是求最小化的 b ,如果我把带选项根据(x,y)写入坐标系中,
就是将斜率为 k 的线段上移,碰到的第一个点,即是最小的b。
那么就讨论什么样的点才能被我第一个碰到
考虑具体实现,知道 sum[i] 单调递增,x 也单调递增
那么就实现一个存放点的序列,满足第一个点是我的最优点。
有以下情况:
1. 当序列中第一个点与第二个点组成的斜率 > k
此时,必定可以取到第一个点。
2. 与上面的情况相反
此时,一定取不到第一个点,那么就把第一个点删去,后面也不可能取到
那么做完这个点后,如何放进我这个序列中满足第一个点是最优决策呢
由图片可知,组成上凸包时,一定无法取到中间那个点
所以直接弹掉。
发现这个序列不就是用单调队列维护么
这种情况下,默认了k递增x递增。
若 k 不递增,那么第一个就不能直接做,而是要二分第一个满足
与前面的点的斜率 < k,与后一个点的斜率 > k 的点。
代码
x 不递增,就将 k ,x 位置调换
若 k ,x都不递增,平衡树维护吧
例题:Cats Transports
剥下这道题的外壳,让它变为一道裸的斜率优化。
很容易想到状态,但复杂度显然过不去,也没有单调性,只能自己创造。
令
$$c[i] = t - sum[i],sum[i] = \sum_{j = 1} ^{i} d[j]$$
如果出发时间为t,那么 t - c[i] 即是等待时间
将 c 数组排序后,带走其中一个即可以带走旁边几个,那就是变成了连续选择,c排序后有了单调性,那么转移式就成了
dp[k][i]表示第 k 个饲养员,到 i 这个地方取猫的最小代价
$$dp[k][i] = min(dp[k - 1][j] + \sum_{t = j + 1}^ic[i] - c[t])(j \le i )$$
发现之中有前缀和
令
$$S_i = \sum_{t = 1}^ic[t]$$
那么化简式子后就成了一个标准的斜率优化
$$dp[k - 1][j] + S_j = c[i] * j + dp[k][i] - c[i] * i$$
代码
Solution
题目中,非常关键的一句,**只能运往编号更大的工厂的仓库**。
这句话直接说明这个就是个最基础的划分 $dp$
$ dp[i] $ 表示选 $i$ 这个位置建仓库的最小费用,那么状态转移如下。
$$dp_i = Min(dp_j + \sum_{k = j}^{i - 1}p[k]*(sum_x[i] - sum_x[k]) + c[i])$$
化简得到
$$sum_p[i] = \sum_1^ip[i];dp_i = dp_j + (sum_p[i] - sum_p[j]) * sum_x[i] - \sum_{k = j}^{i}p[k] * sum_x[k];
$$
发现左边那一部分乘起来的也可用前缀和预先处理,那么便成为了一个标准的斜率优化。
$$
dp_i - sum_x[i]*sum_p[i]+sum_x[i] * sum_p[j] - sum_i = dp_j - sum_j
$$
例题
Solution
此题描述明显划分型 $dp$,主要是在如何优化到 $O(n)$ 上的。
设状态为 $dp[i]$ 表示第 $i$ 个人结尾的最大战斗力
状态转移方程则是:
$$
dp[i] = min(dp[j] + (sum[i] - sum[j])^2 * a + b * (sum[i] - sum[j]) + c),(j\le i)
$$
化简可得
$$
dp_i - a * sum[i]^2 + 2a * sum[i] * sum[j] = dp_j+ a * sum[j]^2 + c - sum[j]*b;
$$
和其它斜率优化题目不一样的是,斜率 $2a * sum[i]$ 是一个负的值并且要取最大。
将点点在坐标系上,因为求的是最大值,所以将一条斜率为负的线从最高向下移动,接触到的第一个点也就是最大值。
由此,单调队列中应该从大到小存放 $y$ 坐标。
考虑具体如何像普通一样的实现,若斜率大于第一个点的斜率,那么必将取到,反之因为单调性可以直接出队列。
那么对于队尾位置,组成下凸时绝对取不到。直接弹出
代码
例题
Solution
此题几个任务,拆开后就是模板了。
一段一段的去柠檬,容易想到划分型。每次取得的 $S_0$ 是区间$(j,i)$的一个众数,容易证明,若当前选择的点是 $i$ 的话,那么被选取得的 $j$ 要满足 $S_j == S_i == S_0$。否则的话,$S_j$ 或 $S_i$被分到其它地方显然是不差于当前决策的。
那么状态是: $dp[i]$ 表示选择到 $i$ 个位置,并且以 $S_i$ 作为 $S_0$。
状态转移就是:
$$
dp[i] = min(dp[j - 1] + s_i * sum(i,j)^2)
$$
个数是 $sum$ 表示的,前面输入时可以预处理出来,$sum(i,j)$ 就成了 $ sum[i] - sum[j] + 1$ 。
此时,不就是类似于玩具装箱的方程式么。考虑斜率优化,化简式子。
$$
dp[i] - s_i * sum[i] ^ 2 + 2s_i(sum[i] + 1)sum[j] = dp_{j-1} + s_j * sum[j] ^ 2;
$$
式子显而易见,但不同的是,我需要求 $max$ 但是 $2 * s_i$ 是一个正的值,单调队列无法解决我想要的结果。
考虑我能求得什么样的点,还是将点放在坐标系上,将一条斜率为 $2 * s_i$ 的线从上往下移动。
发现在 $x$ 越靠前的点不一定最先碰到,而且对于 $x$ 较大的点,组成的斜率若小于当前这个点的是不可能取到的,斜率单调递增,这些点可以直接删去。对于组成下凸的中间的点,肯定也无法取到。
考虑到每一个决策点的 $x$ 与 $y$ 单调递增,并且讨论出来的结果都是在后方删点,也并不在前方取点,而是在后方取到合法点,所以这里用单调栈来解决。
又因为 $S \le 10000$ ,开 $vector$ 解决