斜率优化dp 学习笔记

本文详细介绍了斜率优化动态规划的原理,通过Apio 2010 特别行动队问题为例,阐述了如何将状态转移方程转化为直线形式,并利用单调双端队列维护下凸壳,实现O(n)的时间复杂度解题。此外,还列举了多个相关题目供读者实践。
摘要由CSDN通过智能技术生成

从一个问题开始
真正理解斜率优化dp
orz ISA

1 问题

Apio 2010 特别行动队

1.1 题意简述

给出一个序列 x1,x2...xn ,将其划分成若干个连续的区间,每一段区间 [l,r] 的价值为 ax2+bx+c ,其中 x=i=lrxi
现在你需要最大化序列的价值

1.2 数据范围

对于 20% 的数据, n1000
对于 100% 的数据, 1n106 5a1 |b|107 |c|107 1xi100

1.3 讨论

1.3.1 20pts

f(i) 表示将序列的前i个划分若干段的最大价值, si 表示序列的前缀和
直接根据题意,可以列出状态转移方程
f(i)=max{f(j)+a(sisj)2+b(sisj)+c}1j<i
然后直接dp就可以了
时间复杂度 O(n2)

1.3.2 100pts

数据范围只能允许一个 O(n) 级别的算法

将转移方程进一步展开,能得到
f(i)=max{f(j)+as2i2asisj+as2j+bsibsj+c}1j<i
将其顺序调整,写成下面的形式
f(i)=max{2asjsi+f(j)+as2jbsj+as2i+bsi+c}1j<i
观察这个式子

首先,最后三项,只和 a,b,c 以及 si 有关。换句话说,在求 f(i) 的值时,这三项是与 j 的选取无关的,我们称之为常数项
对于常数项,可以直接将其拿到max之外
f(i)=max{2asjsi+f(j)+as2jbsj}+as2i+bsi+c1j<i

然后再观察前面4项,可以发现 si 是与 j 的选取无关的,只与si的选取有关;而其余都只与 j 的选取有关,而与i的选取无关
si 有什么性质?
si 为正项序列的前缀和,显然它一定是对于 1...n 单调递增的
在求解 f(i) 的过程中,我们也一定是按照 1...n 的顺序求,也就是说,在求解 f(i) 的过程中 si 单调递增

我们考虑将前四项抽象成一条直线
y=kx+b
其中 k=2asj b=f(j)+as2jbsj
也就是说,对于每一个 j(1jn) ,都有一条确定的直线
那么在求解 f(i) 的时候,对于之前的一个确定的 j ,将si带入解析式就能求出这个 j 所对应的函数值
如果枚举每一个j,求出相应的函数值来更新 f(i) 的话,实际上就是 O(n2) dp的方法了
但是我们现在不能枚举,需要直接求出,也就是说,需要确定一条能取到最优值的直线

所以说,什么是斜率优化dp?
对于每一个 i(1in) ,将 i 抽象成一条直线
f(i)时,求之前的所有直线在自变量为某一个数时的最优值
根据直线的斜率和自变量的增减关系,维护出一个 O(n) 的算法
明白了这个之后,就已经基本上明白了斜率优化的大体过程

1.4 题解

对于这道题来说
由于 5a1 ,可以得出 k>0 ,即所有直线的斜率为正
由于 si 单增,求 f(i) 的过程中,顺次加入直线,直线的斜率也始终单增
维护一个斜率单增的单调双端队列,两个指针l,r

每一次加入一条直线 yi 之前,判断队首的两条直线 yl yl+1 si 处的函数值,如果 ylyl+1 ,说明直线 yl 已不是最优值,并且以后也不可能成为最优值,将其出队
计算 f(i) ,此时最优的直线即为队首的直线 yl
加入直线 yi ,如果 yi yr1 已经将 yr 完全覆盖,那么将 yr 弹出

举个栗子
这里写图片描述
在求解 f(3) 的时候,需要查看 f(1) f(2) 所表示的两条直线在 s3 处的函数值,然后取最大
如图所示,显然选 y2 比选 y1 要优
由于 si 单增,以后在求解 f(4)f(5)... 的时候,这两条直线在 s4s5... 处的函数值 y1 也不可能比 y2 更优,所以 y1 实际上就已经可以扔掉了

还有一种情况
这里写图片描述
可以发现,顺次插入 y1,y2,y3 这三条直线,因为要取最大值, y2 实际上已经被 y1 y3 完全覆盖了,无论 si 取何值,它都已经不可能成为最优的答案,这个时候 y2 也可以扔掉了
实际上就是维护了一个下凸壳啊

我刚开始不明白为什么完全覆盖的要扔掉,不扔直接做不行么
这里有一个反例
这里写图片描述
此时 y1 y3 已经将 y2 完全覆盖,应该扔掉
但是如果不扔掉的话,查询此时在 s4 处的最优值
显然 y1>y2 ,这个时候会默认 y1 即为最优值,不会将 y1 弹出
而实际上 y3 才是真正的最优值
所以判断是否有两条直线将另外一条覆盖是完全必要的

如何判断两条直线已经被另一条直线覆盖?
知识储备:初中数学
如上图,令 y1=k1x+b1,y2=k2x+b2,y3=k3x+b3
则求 y1 y3 的交点横纵坐标为
k1x+b1=k3x+b3
x=b3b1k1k3
y=k1b3b1k1k3+b1
然后求 y2 x 处的函数值
y2=k2b3b1k1k3+b2
y2y
k2b3b1k1k3+b2k1b3b1k1k3+b1
b3b1k1k3b2b1k1k2
(k1k3)(b2b1)(k1k2)(b3b1)
那么直线 y2 已经被 y1 y3 完全覆盖
如果斜率为负或者斜率单减的话别搞错了正负号就好

这就是整个斜率优化的过程

1.5 代码

代码简洁清晰明了

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long
#define N 1000005

int n,l,r;
LL a,b,c,x;
int q[N];
LL s[N],f[N];

LL K(int j) {return -2*a*s[j];}
LL B(int j) {return f[j]+a*s[j]*s[j]-b*s[j];}
LL Y(int i,int j) {return K(j)*s[i]+B(j);}
bool cover(int x1,int x2,int x3)
{
    LL w1=(K(x1)-K(x3))*(B(x2)-B(x1));
    LL w2=(K(x1)-K(x2))*(B(x3)-B(x1));
    return w1<=w2;
}
int main()
{
    scanf("%d",&n);
    scanf("%lld%lld%lld",&a,&b,&c);
    for (int i=1;i<=n;++i) scanf("%lld",&x),s[i]=s[i-1]+x;
    l=r=0;
    for (int i=1;i<=n;++i)
    {
        while (l<r&&Y(i,q[l])<=Y(i,q[l+1])) ++l;
        f[i]=Y(i,q[l])+a*s[i]*s[i]+b*s[i]+c;
        while (l<r&&cover(i,q[r],q[r-1])) --r;
        q[++r]=i;
    }
    printf("%lld\n",f[n]);
}

2 斜率优化dp

2.1 常见形式

斜率优化dp的状态转移方程有一些比较特殊的性质:

  • 求解最值
  • 每一个状态能转化成一条直线
  • 在求解某一个状态时,因变量为求解的状态值,自变量为与这个状态相关的量
  • 自变量满足单调性,斜率满足单调性

2.2 基本方法

  • 将状态转移方程化成能表示成直线的形式
  • 找出自变量、因变量、斜率、截距,讨论自变量的单调性、斜率的正负及单调性
  • 维护单调双端队列,维护上凸/下凸壳

3 相关题目

题目难度尽量递增
ps 原来写的代码都好丑

bzoj1597 土地购买
http://blog.csdn.net/clove_unique/article/details/51244336
bzoj3156 防御准备
http://blog.csdn.net/clove_unique/article/details/51250213
bzoj3437 小P的牧场
http://blog.csdn.net/clove_unique/article/details/51297305
bzoj1010 hnoi2008 玩具装箱
http://blog.csdn.net/Clove_unique/article/details/51225398
bzoj3675 apio2014 序列分割
http://blog.csdn.net/clove_unique/article/details/51297313
bzoj1096 zjoi2007 仓库建设
http://blog.csdn.net/clove_unique/article/details/51244357
bzoj4518 sdoi2016 征途
http://blog.csdn.net/clove_unique/article/details/51224588

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值