斜率优化DP
例题[P2900 USACO08MAR] Land Acquisition G - 洛谷
简要题意:
- 有 \(n\) 块大小为 \(w \times l\) 的土地,现在要购买所有土地,可以分组购买
- 对于每一组土地的价值定义为 \(max_w \times max_l\) ,即该组土地内所有土地最大的 \(w\) 乘最大的 \(l\)
- 求最小价值
初步分析
我们能够发现对于部分土地是没有价值的,即如果 \(l_i < l_j\) 且 \(w_i < w_j\) ,那么土地 \(i\) 是显然不会产生价值的,那么我们可以去掉它们
先以 \(w\) 从小到大排序,若 \(w\) 相同,则以 \(l\) 从大到小排序
然后从后往前遍历,保证了 \(w_i < w_{i + 1...n}\),取当前最大的 \(l_{max}\) ,若 \(l_i < l_{max}\) ,则显然 \(l_i\) 可以删除
此时我们发现 \(w_1 < w_2 < w_3 ...w_{n-1} < w_n\), \(l_1 > l_2 > l_3 > ... > l_{n - 1} > l_n\)
此时我们再列 \(dp\) 式子得到:
\(dp[i] = min(dp[j] + w[i] \times l[j + 1])\)
但我们发现这里的 \(w[i] \times l[j + 1]\) 由于同时牵扯了 \(i,j\) 所以导致我们似乎无法直接快速求出 \(dp_i\) ,所以我们引出斜率优化
斜率优化部分
我们发现对于这个式子 \(dp_j + w[i] \times l[j + 1])\) 我们可以表达成一条直线
这里固定 \(j\) 为定值,则 \(y_j = dp[j] + w[i] \times l[j + 1]\)
其中 \(k = l[j + 1]\) , \(x = w[i]\) ,\(b = dp[j]\)
这里我们用了逆向思维,将 \(j\) 固定,\(i\) 看为变量,对于每一个 \(j\) , 构造一条直线
则此就变成了寻找对于当前 \(x\) 能使 \(y\) 坐标最小的直线
然后,该怎么找这个直线呢?
我们考虑维护一个凸包,从而直接找到在某个区间内的最优解
这里我们维护的是直线之间的交点所构成的凸包
后面我将根据示例代码讲解
...省略
db slope(int x, int y){
return 1.0 * (db(dp[x] - dp[y]) / db(a[y + 1].l - a[x + 1].l));
}
ll get_y(int i, int j){
return a[j + 1].l * a[i].w + dp[j];
}
int main(){
...省略
int l = 0, r = 0;
q[0] = 0;
for(int i = 1; i <= len; i++){
while(l < r && get_y(i, q[l]) >= get_y(i, q[l + 1]))l++;
dp[i] = get_y(i, q[l]);
while(l < r && slope(q[r - 1], q[r]) >= slope(q[r], i))r--;
q[++r] = i;
}
cout << dp[len];
return 0;
}
我们这里采用的是单调队列维护凸包
我们先来看维护队首这里
while(l < r && get_y(i, q[l]) >= get_y(i, q[l + 1]))l++;
这里的 get_y 值得是当获取直线 \(l\) 其 \(x\) 所对应的 \(y\) 值
因为我们当前在解决 \(x = i\) 的情况,所以如果队首直线对应 \(y\) 值大于下一条直线则弹出
那为什么是正确的?这条直线不会对其它 \(x\) 产生贡献吗?
因为 \(x = w[i]\) 是单调递增的,且由于凸包的性质,这条直线只会在其对应区间内最优,一旦 \(x\) 在区间右侧之后这条直线就不会再产生贡献
然后中间就是直接 \(dp\) ,最优直线就是队首
最后看加入队尾的部分
while(l < r && slope(q[r - 1], q[r]) >= slope(q[r], i))r--;
这里首先要说,这里的 \(slope\) 并不是算的斜率,而是交点(虽然 \(slope\) 翻译过来就是斜率,但是我也不知道为什么那么多人都在写 \(slope\) ),这道题中的斜率本身就是单调递减的,且凸包是由点构成的,所有我们并不需要维护斜率,我们维护的是直线之间的交点,\(slope\) 返回的是交点的横坐标
那我们为什么要比较横坐标呢?
还是因为斜率在单调递减,所以如果直线 \(r - 1\) 与直线 \(r\) 的交点 \(x_1\) 的横坐标大于直线 \(r\) 与新直线的交点 \(x_2\) 的横坐标,那么直线 \(r\) 就无用了,因为凸包的性质所以在 \(x_1\) 左侧直线 \(r - 1\) 显然更小,又因为新直线斜率小,所以 \(x_2\) 右侧新支线显然 \(y\) 值更小
这里给一张图片方便理解


浙公网安备 33010602011771号