【易懂】斜率DP
前言
首先此篇文章是为低年级的朋友准备的,不涉及什么深奥的知识,比如线性规划之类的。仔细看,不要以为自己学不会,看不懂,只要你会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\)中至少有一个是单调递增/递减的(如果没有,据说可以用一些高级算法维护)。
- 如果有一个是单调的,可以优化到\(O(n \log_2 n)\);
- 如果有两个是单调的,也就是说满足决策单调性(以上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\),也就是费用更少。可以列出下面式子:
即: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\),我们必须保证正确性,于是列出三种情况):
- \(k2<k1 \leq k0\),由上述结论知,\(j3优于j2优于j1\);
- \(k2 \leq k0 <k1\),由上述结论知,\(j3和j1优于j2\);
- \(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的更多相关文章
- bzoj4518: [Sdoi2016]征途--斜率DP
题目大意:把一个数列分成m段,计算每段的和sum,求所有的sum的方差,使其最小. 由方差*m可以化简得ans=m*sigma(ki^2)-sum[n]^2 很容易得出f[i][j]=min{f[i- ...
- hdu 3507 斜率dp
不好理解,先多做几个再看 此题是很基础的斜率DP的入门题. 题意很清楚,就是输出序列a[n],每连续输出的费用是连续输出的数字和的平方加上常数M 让我们求这个费用的最小值. 设dp[i]表示输出前i个 ...
- 斜率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] * ...
- 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] ...
- [kuangbin带你飞]专题二十 斜率DP
ID Origin Title 20 / 60 Problem A HDU 3507 Print Article 13 / 19 Problem B HDU 2829 Lawr ...
- 斜率DP题目
uva 12524 题意:沿河有n个点,每个点有w的东西,有一艘船从起点出发,沿途可以装运东西和卸载东西,船的容量无限,每次把wi的东西从x运到y的花费为(y-x)*wi; 问把n个点的东西合并成k个 ...
- bzoj 3156 防御准备(斜率DP)
3156: 防御准备 Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 837 Solved: 395[Submit][Status][Discuss] ...
- [DP优化方法之斜率DP]
什么是斜率dp呢 大概就把一些单调的分组问题 从O(N^2)降到O(N) 具体的话我就不多说了 看论文: http://www.cnblogs.com/ka200812/archive/2012/08 ...
- hdu2993之斜率dp+二分查找
MAX Average Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Othe ...
随机推荐
- Python 模拟登录几种常见方法
方法一:直接使用已知的cookie访问 优点: 简单,但需要先在浏览器登录 原理: 简单地说,cookie保存在发起请求的客户端中,服务器利用cookie来区分不同的客户端.因为http是一种无状态的 ...
- IP multicast IP多播
https://networklessons.com/multicast/multicast-routing/ IP多播有两种模式,密集模式和稀疏模式: Dense Mode Sparse Mode ...
- 使用Unicode(宽字节字符集);多字节字符集中定义宽字节变量
2012-03-25 14:54 (分类:计算机程序) 2.2 宽字符和C 宽字符不一定是Unicode.Unicode是宽字符集的一种.然而,因为本书的焦点是Windows而不是C执行的理论,所以书 ...
- 使用Git和Svn
一. 使用SVN 1. 下载tortoiseSVN 2. 右键SVN checkout(下载项目到本地) 3. 更新和提交 二. 使用GIT 1. 下载git 2. 下载tortoiseGit 3. ...
- React之props、state和render函数的关系
1.当组件中的state或者props发生改变的的时候,render函数就会被重新执行 2.当父组件的render函数被运行时,它的子组件的render都将被重新运行一次 3.子组件作为父组件里的一个 ...
- K8S ConfigMap使用
k8s系列文章: 什么是K8S configmap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息,加密信息则需用到Secret,主要用来应对以下 ...
- 惊讶!缓存刚Put再Get居然获取不到?
最近一直在老家远程办公,微信突然响了下,有同事说遇到了一个奇怪的问题,让我帮忙看下. 现象就是标题所说的缓存获取不到的问题,我一听感觉这个问题挺有意思的,决定一探究竟. 下面给出部分代码还原下案发现场 ...
- ES6学习笔记(二):教你玩转类的继承和类的对象
继承 程序中的继承: 子类可以继承父类的一些属性和方法 class Father { //父类 constructor () { } money () { console.log(100) } } c ...
- C# DateTime 工具类
项目gitHub地址 点我跳转 今天给大家带来一个C#里面的时间工具类,具体的直接看下面代码 using System; namespace ToolBox.DateTimeTool { public ...
- $.getJSON获取json数据失败
首先简单介绍下 $.ajax $.get $.post $.getJSON 的区别和用法 $.ajax中有一个type属性,专门用来指定是get请求还是post请求的分别对应的就是$.get和$ ...