前言

首先此篇文章是为低年级的朋友准备的,不涉及什么深奥的知识,比如线性规划之类的。仔细看,不要以为自己学不会,看不懂,只要你会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. Python 模拟登录几种常见方法

    方法一:直接使用已知的cookie访问 优点: 简单,但需要先在浏览器登录 原理: 简单地说,cookie保存在发起请求的客户端中,服务器利用cookie来区分不同的客户端.因为http是一种无状态的 ...

  2. IP multicast IP多播

    https://networklessons.com/multicast/multicast-routing/ IP多播有两种模式,密集模式和稀疏模式: Dense Mode Sparse Mode ...

  3. 使用Unicode(宽字节字符集);多字节字符集中定义宽字节变量

    2012-03-25 14:54 (分类:计算机程序) 2.2 宽字符和C 宽字符不一定是Unicode.Unicode是宽字符集的一种.然而,因为本书的焦点是Windows而不是C执行的理论,所以书 ...

  4. 使用Git和Svn

    一. 使用SVN 1. 下载tortoiseSVN 2. 右键SVN checkout(下载项目到本地) 3. 更新和提交 二. 使用GIT 1. 下载git 2. 下载tortoiseGit 3. ...

  5. React之props、state和render函数的关系

    1.当组件中的state或者props发生改变的的时候,render函数就会被重新执行 2.当父组件的render函数被运行时,它的子组件的render都将被重新运行一次 3.子组件作为父组件里的一个 ...

  6. K8S ConfigMap使用

    k8s系列文章: 什么是K8S configmap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息,加密信息则需用到Secret,主要用来应对以下 ...

  7. 惊讶!缓存刚Put再Get居然获取不到?

    最近一直在老家远程办公,微信突然响了下,有同事说遇到了一个奇怪的问题,让我帮忙看下. 现象就是标题所说的缓存获取不到的问题,我一听感觉这个问题挺有意思的,决定一探究竟. 下面给出部分代码还原下案发现场 ...

  8. ES6学习笔记(二):教你玩转类的继承和类的对象

    继承 程序中的继承: 子类可以继承父类的一些属性和方法 class Father { //父类 constructor () { } money () { console.log(100) } } c ...

  9. C# DateTime 工具类

    项目gitHub地址 点我跳转 今天给大家带来一个C#里面的时间工具类,具体的直接看下面代码 using System; namespace ToolBox.DateTimeTool { public ...

  10. $.getJSON获取json数据失败

    首先简单介绍下 $.ajax  $.get  $.post  $.getJSON 的区别和用法 $.ajax中有一个type属性,专门用来指定是get请求还是post请求的分别对应的就是$.get和$ ...