前言

首先此篇文章是为低年级的朋友准备的,不涉及什么深奥的知识,比如线性规划之类的。仔细看,不要以为自己学不会,看不懂,只要你会DP并打过一些题目而且会单调队列优化DP,斜率DP离你就不远了~~~。这篇文章也是在我领悟了斜率DP不久写的,如果本文有什么不严谨的地方,欢迎指出!!!在此推荐一个大佬的BLOG,https://www.cnblogs.com/Xing-Ling/p/11210179.html,讲得很详细,如果你理解能力稍微强一点,可以看这篇文章,本文取自这篇博客的精华外加自己感受,换了一个简单点的题目方便理解。

斜率DP介绍

“斜率DP”,顾名思义就是通过斜率来优化DP,斜率式子\(\frac{y_1-y_2}{x_1-x_2}\)表示\(x_1,y_1\)和\(x_2,y_2\)两点的斜率。可以用斜率优化的式子通常可以写成\(f[i]=min/max(f[j]+P(i)*Q(j))+H(i)\),\(P\)和\(H\)是只与\(i\)有关的函数,\(Q\)是只与\(j\)有关的函数,单调队列优化解决不了的就在于既包含\(i\)又包含\(j\)的这一项\(P(i)*Q(j)\),于是诞生了斜率DP这么个东西,当然它对这个式子也有一个特殊要求,\(P\)和\(Q\)中至少有一个是单调递增/递减的(如果没有,据说可以用一些高级算法维护)。

  1. 如果有一个是单调的,可以优化到\(O(n \log_2 n)\);
  2. 如果有两个是单调的,也就是说满足决策单调性(以上BLOG有讲),可以优化到\(O(n)\)。

至于为什么,请听下面分析。

理解斜率DP

这里以NOIP2010 四校联考模拟一 city为例。题目大意:有\(n\)个点,从第\(i\)个点到第\(j\)个点(\(i<j\))的费用为\((j-i)*a[i]+b[j]\),求从第\(1\)个点到第\(n\)个点的最小费用。这题描述很简单。

那么由题列出的状态转移方程为\(f[i]=min(f[j]+j*a[i]+b[j])-i*a[i](i<j)\),可能看到这你有点疑问吧,为了让这题可做,我们从\(n\)~\(1\)枚举\(i\),至于为什么,学会了你可以自己顺着试试。我们把只含\(i\)的项提了出来,因为在每次算\(f[i]\)时是不变的。普通做法的时间复杂度显然是\(O(n^2)\)的,显然是过不了的。

设\(j,k(0<i<k<j \leq n)\)为\(i\)的决策点一定要注意这里的大小关系),也就是可以转移到\(i\)的两点,且决策点\(j\)优于(或“相等“)\(k\),也就是费用更少。可以列出下面式子:

\[f[j]+a[i]*j+b[j] \leq f[k]+a[i]*k+b[k]\\
即:a[i]*j \leq f[k]+a[i]*k+b[k]-f[j]-b[j]\\
a[i]*j-a[i]*k \leq f[k]+b[k]-f[j]-b[j]\\
(j-k)*a[i] \leq f[k]+b[k]-(f[j]+b[j])\\
a[i] \leq \frac{f[k]+b[k]-(f[j]+b[j])}{j-k}(注:因为j \neq k,所以可以移到右边)\\
设g[j]=f[j]+b[j]\\
a[i] \leq \frac{g[k]-g[j]}{j-k}\\
-a[i] \geq \frac{g[j]-g[k]}{j-k} (注:这一步是为了让上下匹配)\\
\]

结论我们得知,如果出现了这种情况\(-a[i] \geq \frac{g[j]-g[k]}{j-k}\),\(j\)是优于(包括相等)\(k\)的,同理,如果是\(-a[i] \leq \frac{g[j]-g[k]}{j-k}\),则是\(k\)(包括相等)优于\(j\)。我们把\(j,k\)看成x坐标;\(g[j],g[k]\)看做y坐标,则代表决策点\(j\)的点的坐标为\((j,g[j])\),\(k\)为\((k,g[k])\)。**划重点(引用上文的博客,有改动):此处移项需要遵循的原则是:参变分离。将只与\(i\)有关的视作未知量,用其他的量来表示出只与\(i\)有关的量。最后的公式尽量化成\(\frac{y_1-y_2}{x_1-x_2}\),而不是\(\frac{y_1-y_2}{x_2-x_1}\),对于这种情况我们可以两边\(*-1\),注意要变号 **!!!

维护凸包

那么我们使用这个式子,维护一个凸包(用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点——百度百科)。

设有三个点\(j1,j2,j3\),他们都是已经求出了值的。\(k1,k2\)代表斜率。很明显可以看出\(k2<k1\),我们设\(k0=-a[i]\)。有以下三种情况(为了应对以后不同大小的\(k0\),我们必须保证正确性,于是列出三种情况):

  1. \(k2<k1 \leq k0\),由上述结论知,\(j3优于j2优于j1\);
  2. \(k2 \leq k0 <k1\),由上述结论知,\(j3和j1优于j2\);
  3. \(k0 \leq k2 <k1\),由上述结论知,\(j1优于j2优于j3\);

综上,无论哪种情况\(j2\)这个决策点都不是最优的,也就是在后续的DP中,不论\(k0\)多大,\(j2\)永远不会被用于更新一个点,它是没用的,我们把它删去。

这个有什么用呢?维护凸包,说白了就是维护单调队列优化那样的队列。这个队列中存在一个最优决策点,就是最优的那个,队列中的每个点都有可能为后续的DP做贡献。我们在\(f[i]\)求完以后将它加入队列(如图的\(j3\)),并删除没用的点(如图的\(j2\))。在此贴上本题本部分代码

while(head<tail&&slope(q[tail-1],q[tail])<=slope(q[tail-1],i)) tail--;//slope函数是计算x,y两点的斜率;必须保证队列里至少有2个点
q[++tail]=i;//q是队列,存的是点的编号

二分答案

我们求\(f[i]\)的时候,队列里必定存在着一个最优决策点,从它转移到\(i\)是最优的,通过以上的维护凸包,我们发现,对于这题我们维护的是下凸包(很形象,就是凸向下的),如果对于这题求花费最大,也就是把结论中的符号反过来维护的就是上凸包。不改变这题,相邻两个点间的斜率是递增的,如下图:

由于斜率是单调递增的,我们可以二分答案,那我们要二分找啥呢?

如上图:\(k1<k2<k3<k4<k5<k6<k7\),假设\(k0\)的大小是这样的:\(k1<k2<k3 \leq k0<k4<k5<k6\)

我们发现,\(k3 \leq k0\),由上述结论知,\(j4优于j3\);\(k0<k4\),由上述结论知,\(j4优于j5\)。其他以此类推。所以当前图中\(j4\)是最优决策点,我们二分一条线段的右端点,找到第一个斜率\(>k0\)的,它的上一个节点(也就是那条线段的左端点)即是最优决策点。如此,可以用\(\log_2 n\)的时间求出最优决策点。需要注意的是二分这里有很多细节,我之前是二分左端点的,但是当所有的斜率都小于\(k0\)的时候,实际上最优的是最后一个点,但是我的程序二分到了倒数第二个端点,于是错了,卡了我好久,后开加了个特判过了。贴上代码(因为加了特判,写的很丑,见谅!同学们可以二分右端点):

l=head;
r=tail-1;
if(l==r){
if(slope(q[l],q[l+1])<-a[i]) l++;//只有两个点时无法二分
}else{
while(l<r){
mid=(l+r-1)/2;
if(slope(q[mid],q[mid+1])<-a[i]) r=mid;
else l=mid+1;
}
if(tail>head)
if(slope(q[l],q[l+1])>=-a[i]) l++;//特殊情况判断
}
//l即为最优决策点在队列中的下标
f[i]=f[q[l]]+(q[l]-i)*a[i]+b[q[l]];//用最优决策点更新f[i]

总结

实际上,斜率DP不是一个太难的东西,你可以把它想象成单调队列优化的工作模式,它的核心也是一个单调队列,但是它维护的规则与单调队列优化不一样,单调队列优化只是简单地基于数值维护,而斜率优化则是通过斜率维护,他们每加入一个新点时都要删去一些点。附上完整代码:

#include<cstdio>
int n,l,r,mid,head,tail,q[100005];
long long a[100005],b[100005],f[100005];
inline long double slope(int j,int k) {return (long double)((f[j]+b[j])-(f[k]+b[k]))/(long double)(j-k);}
int main(){
freopen("t3.in","r",stdin);
freopen("t3.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%lld",&b[i]);
f[n]=0;
head=tail=1;
q[1]=n;
for(int i=n-1;i>=1;i--){
l=head;
r=tail-1;
if(l==r){
if(slope(q[l],q[l+1])<-a[i]) l++;
}else{
while(l<r){
mid=(l+r-1)/2;
if(slope(q[mid],q[mid+1])<-a[i]) r=mid;
else l=mid+1;
}
if(tail>head)
if(slope(q[l],q[l+1])>=-a[i]) l++;
}
f[i]=f[q[l]]+(q[l]-i)*a[i]+b[q[l]];
while(head<tail&&slope(q[tail-1],q[tail])<=slope(q[tail-1],i)) tail--;
q[++tail]=i;
}
printf("%lld",f[1]);
return 0;
}

【易懂】斜率DP的更多相关文章

  1. bzoj4518: [Sdoi2016]征途--斜率DP

    题目大意:把一个数列分成m段,计算每段的和sum,求所有的sum的方差,使其最小. 由方差*m可以化简得ans=m*sigma(ki^2)-sum[n]^2 很容易得出f[i][j]=min{f[i- ...

  2. hdu 3507 斜率dp

    不好理解,先多做几个再看 此题是很基础的斜率DP的入门题. 题意很清楚,就是输出序列a[n],每连续输出的费用是连续输出的数字和的平方加上常数M 让我们求这个费用的最小值. 设dp[i]表示输出前i个 ...

  3. 斜率dp cdq 分治

    f[i] = min { f[j] + sqr(a[i] - a[j]) } f[i]= min { -2 * a[i] * a[j] + a[j] * a[j] + f[j] } + a[i] * ...

  4. HDU 2829 Lawrence (斜率DP)

    斜率DP 设dp[i][j]表示前i点,炸掉j条边的最小值.j<i dp[i][j]=min{dp[k][j-1]+cost[k+1][i]} 又由得出cost[1][i]=cost[1][k] ...

  5. [kuangbin带你飞]专题二十 斜率DP

            ID Origin Title   20 / 60 Problem A HDU 3507 Print Article   13 / 19 Problem B HDU 2829 Lawr ...

  6. 斜率DP题目

    uva 12524 题意:沿河有n个点,每个点有w的东西,有一艘船从起点出发,沿途可以装运东西和卸载东西,船的容量无限,每次把wi的东西从x运到y的花费为(y-x)*wi; 问把n个点的东西合并成k个 ...

  7. bzoj 3156 防御准备(斜率DP)

    3156: 防御准备 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 837  Solved: 395[Submit][Status][Discuss] ...

  8. [DP优化方法之斜率DP]

    什么是斜率dp呢 大概就把一些单调的分组问题 从O(N^2)降到O(N) 具体的话我就不多说了 看论文: http://www.cnblogs.com/ka200812/archive/2012/08 ...

  9. hdu2993之斜率dp+二分查找

    MAX Average Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Othe ...

随机推荐

  1. RocketChat:开源聊天软件部署

    1. 简介 RocketChat是一款优秀的开源聊天软件.支持各种平台,IOS.Android.Web.Mac.Windows以及Linux,安装部署简单,功能简单易用,目前部分功能还在完善当中,特别 ...

  2. mysql添加远程权限

    mysql -u root -p grant all privileges on *.* to root@'%' identified by "password"; flush p ...

  3. win10双击执行python

    一. 设置py环境 去官网下载Windows x86-64 executable installer安装 安装后会自动配置py的bin路径和pip的路径 Pip用于安装python库的 二. 设置wi ...

  4. PBFT 算法 java实现(下)

    PBFT 算法的java实现(下) 在上一篇博客中(如果没有看上一篇博客建议去看上一篇博客),我们介绍了使用Java实现PBFT算法中节点的加入,view的同步等操作.在这篇博客中,我将介绍PBFT算 ...

  5. mac item2的快捷键

    https://juejin.im/entry/58fac23fb123db4449071c99 听说这个工具可以解决,iterm2的整句翻译的问题.一致找不到免费的破解版本. Myna for Go ...

  6. IntelliJ 更改项目使用的 JDK 版本

    在当前使用的 IntelliJ 中的 JDK 版本为 1.8,如何修改 IntelliJ 使用的 JDK 版本为 1.11 呢? 你可以在 IntelliJ 中进行修改. 选择 File 后,然后选择 ...

  7. C# 中Trim()、TrimStart()、TrimEnd()、ToUpper()、ToLower()的用法

    Trim():删除字符串头部及尾部出现的空格,删除的过程为从外到内,直到碰到一个非空格的字符为止,所以不管前后有多少个连续的空格都会被删除掉. TrimStart():只删除字符串的头部的空格. Tr ...

  8. Uncaught TypeError: Cannot set property 'onclick' of null解决办法

    如果把js内容直接放在这个head标签以内,button按钮不能正常点击更换body的背景颜色,报错提示:demo6.html:16 Uncaught TypeError: Cannot set pr ...

  9. Android Studio 学习笔记(五):WebView 简单说明

    Android中一个用于网页显示的控件,实际上,也可以看做一个功能最小化的浏览器,看起来类似于在微信中打开网页链接的页面.WebView主要用于在app应用中方便地访问远程网页或本地html资源.同时 ...

  10. MySQL中的执行计划explain

    一.用法及定义: explain为sql的执行计划.在sql前面加上explain关键字即可 如:explain select * from tbl_emp; 名词解释: id:[操作表的顺序] 1. ...