斜率DP总结

chunlvxiong的博客


T1:防御准备

    三个月后第一次写博客,我们从这个题开始:http://www.lydsy.com/JudgeOnline/problem.php?id=3156。

  这道题DP方程比较好写:用dp[i]表示1到i全部被控制的最小代价,那么dp[i]=min{dp[j]+(i-j)*(i-j-1)/2+a[i]}/*表明j+1到i被i守卫*/

  然后O(N^2)大T特T。

  这里就要用到斜率优化DP,下面给出我推导这道题的过程。

  设i从j转移比从k转移要优,那么:

  dp[j]+(i-j)*(i-j-1)/2<dp[k]+(i-k)*(i-k-1)/2
  dp[j]+[i^2-2ij+j^2-i+j]/2<dp[k]+[i^2-2ik+k^2-i+k]/2
  dp[j]-dp[k]<[-2ik+k^2+k+2ij-j^2-j]/2
  dp[j]-dp[k]<[k(k+1)-j(j+1)]/2-(ik-ij)
  i(k-j)<{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}
  j<k:{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}/(k-j)>i-->{j(j+1)/2+dp[j]}-{k(k+1)/2+dp[k]}/(j-k)>i
  j>k:{k(k+1)/2+dp[k]}-{j(j+1)/2+dp[j]}/(k-j)<i-->{j(j+1)/2+dp[j]}-{k(k+1)/2+dp[k]}/(j-k)<i

  由此可以得到一个很像斜率的东西:令yi=i(i+1)/2+dp[i],xi=i,那么你发现这个式子变成了:

  j<k:(yj-yk)/(xj-xk)>i

  j>k:(yj-yk)/(xj-xk)<i

  这个东西维护起来要好很多,因此yj,yk,xj,xk都是不受i的影响的,如果你能把式子化成类似这样的形式,那么你几乎已经成功了。

  一般斜率DP使用单调队列进行维护。(下面描述中,我们用g(a,b)表示(ya-yb)/(xa-xb))

  队头维护:本题中优于i是递增的,用a表示队列第一项,用b表示队列第二项(a<b),那么g(b,a)<i时,则以后g(b,a)一定一直小于i,也就是说以后b一定一直优于a,那么可以将a弹出。

  队尾维护:(这个你也可以画图维护一个类似凸包的东西,但我更喜欢直接推)

  用a表示队尾倒数第二项,用b表示队尾最后一项,用c表示当前要插入的元素(a<b<c)。

  可以发现当g(a,b)>g(b,c)时,b不可能成为最优解。

  1、当g(a,b)>i,表明a优于b,b不是最优解。

  2、当g(a,b)<i,则g(b,c)<g(a,b)<i,表明b劣于c,b不是最优解。

  因此可以将b弹出。

  转移的时候直接取出队头元素进行转移即可,整个DP复杂度变为O(N),A掉此题。

  注意点:上述过程中,请重视正负性的问题,这也许会导致不等式变号,从而改变整个式子。

  来个简单点的题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1597(其实这道才是我的入门题啊!)

贴代码:(注:可以用乘积式代替g函数)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1000005;
int n,front,rear;
ll a[maxn],dp[maxn],q[maxn];
ll y(ll a){return a*(a+1)/2+dp[a];
}
ll x(ll a){return a;
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%lld",&a[i]);dp[0]=0;front=rear=0,q[rear++]=0;for (ll i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])<1.0*i) front++;ll t=q[front]; dp[i]=dp[t]+(i-t)*(i-t-1)/2+a[i];while (front<rear-1 && g(q[rear-2],q[rear-1])>g(q[rear-1],i)) rear--;q[rear++]=i;}printf("%lld\n",dp[n]);return 0;
}

T2:小P的牧场

  题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3437

  这题比上一题不同之处在于,它到控制它的控制站之间的牧场数目(不包括自身,但包括控制站所在牧场)乘上该牧场的放养量这句话有点碍眼。

  单单使用一个sum前缀和似乎不能表示出DP方程。

  如果i控制j+1到i之间的牧场,那么新的cost就是(i-j-1)*b[j+1]+(i-j-2)*b[j+2]+……+1*b[i-1]+0*b[i]+a[i](a[i]后面忽略不计)

  你发现b[j+1]到b[i]的系数刚好都是-1下去的,所以你考虑维护一个square数组,square[i]=Σb[x]*x|1<=x<=i。

  然后square[i]-square[j]=b[i]*i+b[i-1]*(i-1)+……+b[j+1]*(j+1)。可以用(sum[i]-sum[j])*i-(square[i]-square[j])来表示上面那个式子。

  那么DP方程写出来了,用dp[i]表示1到i的站被控制的代价,那么dp[i]=min{dp[j]+(sum[i]-sum[j])*i-(square[i]-square[j])+a[i]}

  请仿照上题自行整理成斜率DP的形式,顺便总结一下:

  1、首先要化成(yj-yk)/(xj-xk)<a或者>a的形式,其中yi,xi是只跟i有关的式子,为了化成这样的式子,有时还需要引进前缀和等数组。

  2、队头处理:考虑a递增或是递减的性质,然后通过g(x,y)与a的关系来判断是否弹出x。

  3、队尾处理:类似于几何上凸包的形状,尽管我个人直接推导更不容易错,即g(x,y)<g(y,z)或g(x,y)>g(y,z)的形式。

  4、DP时直接取出队头元素计算即可。

  5、千万注意正负性问题,这是最大的易错点。(当初之所以搞不懂斜率DP就是因为忽视了正负性的因素)

  类似的一题:http://www.lydsy.com/JudgeOnline/problem.php?id=1096

  再来一题:http://www.lydsy.com/JudgeOnline/problem.php?id=1010

贴本题代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1000005;
int n,front,rear,q[maxn];
ll a[maxn],b[maxn],sum[maxn],square[maxn],dp[maxn];
ll x(ll a){return sum[a];
}
ll y(ll a){return dp[a]+square[a];
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d",&n);for (int i=1;i<=n;i++) scanf("%lld",&a[i]);square[0]=sum[0]=0;for (ll i=1;i<=n;i++){scanf("%lld",&b[i]);sum[i]=sum[i-1]+b[i],square[i]=square[i-1]+b[i]*i;}dp[0]=0;front=rear=0,q[rear++]=0;for (ll i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])<1.0*i) front++;int j=q[front]; dp[i]=dp[j]-(square[i]-square[j])+i*(sum[i]-sum[j])+a[i];while (front<rear-1 && g(q[rear-2],q[rear-1])>g(q[rear-1],i)) rear--;q[rear++]=i;}printf("%lld\n",dp[n]);return 0;
}

T3:[Apio2014]序列分割

  题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3675

  这个题比较复杂,我们慢慢推。

  最暴力的DP:需要三维,分别存储本次割点,上次割点,分割次数,然后再穷举上上次割点,进行转移,复杂度O(N^3K)=T到无边无际。

  然后第一步非常的巧,我们画个图来说明吧:

  

  割裂顺序有两种,得到的价值分别如下:

  1、先割裂sum1和sum2,再割裂sum2和sum3,价值为sum1*(sum2+sum3)+sum2*sum3

  2、先割裂sum2和sum3,再割裂sum1和sum2,价值为sum3*(sum1+sum2)+sum1*sum2

  发现了什么-->两种割裂方式价值是一样的-->确定了割裂位置的话,割裂得到价值与割裂顺序无关。

  这个结论的好处就是我们可以直接从开头一刀刀割向结尾,DP方程变为:(用dp[i][c]表示序列前i项已经割好,且割了c次的最大价值)

  利用前缀和优化:dp[i][c]=max{dp[j][c-1]+(sum[i]-sum[j])*sum[j]}

  复杂度是O(N^2K)的,仍然T的厉害,我们必须再去掉一个N,O(NK)才能A掉此题。

  利用上述的斜率优化尝试一下:

  dp[j][c-1]+(sum[i]-sum[j])*sum[j]>dp[k][c-1]+(sum[i]-sum[k])*sum[k]

  (dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])

  令yi=dp[i][c-1]-sum[i]^2,xi=sum[i]

  j<k:(yj-yk)/(xk-xj)>sum[i]-->(yj-yk)/(xj-xk)<-sum[i]

  j>k:(yj-yk)/(xk-xj)<sum[i]-->(yj-yk)/(xj-xk)>-sum[i]

  后面队头队尾的维护请自行推导,复杂度可以少一个N,O(NK)应该能过去。

  本题空间限制128MB,如果开一个100000*200的long long数组=MLE,因此需要滚动数组。

  但是这题我调了很久,下面来好好说一说关于0的问题(本题(xj-xk)可能为0)

  到(dp[j][c-1]-sum[j]^2)-(dp[k][c-1]-sum[k]^2)>sum[i]*(sum[k]-sum[j])这一步为止,我们只进行加减法,所以这一步的式子是可靠的。

  那么xj-xk=0,所以要判断yj-yk是否大于0即可,如果yj-yk>0,那么无论sum[i]等于几,j都优于k。

  好像用乘积式可以解决问题,但是我用乘积式一直WA,所以换了一种更好的解决0问题的方法(对于本题而言),由于本题0的存在毫无意义,在输入时把ai=0的全部删去,以保证xj-xk不等于0。

贴代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,k,front,rear,q[maxn];
ll a[maxn],sum[maxn],dp[maxn],Dp[maxn];
ll x(ll a){return sum[a];
}
ll y(ll a){return dp[a]-sum[a]*sum[a];
}
double g(ll a,ll b){return (1.0*(y(a)-y(b)))/(1.0*(x(a)-x(b)));
}
int main(){scanf("%d%d",&n,&k),sum[0]=0;for (int i=1;i<=n;i++){scanf("%lld",&a[i]);if (!a[i]) n--,i--; else sum[i]=sum[i-1]+a[i];}memset(dp,0,sizeof(dp));for (int c=1;c<=k;c++){front=rear=0,q[rear++]=0;for (int i=1;i<=n;i++){while (front<rear-1 && g(q[front+1],q[front])>-sum[i]) front++;int j=q[front]; Dp[i]=dp[j]+(sum[i]-sum[j])*sum[j];while (front<rear-1 && g(q[rear-2],q[rear-1])<g(q[rear-1],i)) rear--;q[rear++]=i;}for (int i=1;i<=n;i++) dp[i]=Dp[i];}printf("%lld\n",Dp[n]);return 0;
}

下面给一道相对简单的题目:http://www.lydsy.com/JudgeOnline/problem.php?id=1911

转载于:https://www.cnblogs.com/chunlvxiong/p/8078426.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/251262.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端使用react-intl-universal进行国际化

一、国际化 / i18n 目前国际化&#xff0c;就是开发者写对象&#xff0c;一个key关联若干语种的翻译。相比于浏览器自带的翻译功能&#xff0c;语义更加准确。 “国际化”的简称&#xff1a;i18n&#xff08;其来源是英文单词 internationalization的首末字符i和n&#xff0c;…

守护线程Daemon的理解

1、守护线程伴随着主线程的销毁而销毁&#xff1b; 2、jvm虚拟机中有很多守护线程&#xff0c;随着main函数的结束而结束&#xff0c;自动回收栈中的内容。 Thread t1 new Thread(){Overridepublic void run() {for (int i 0; i < 10; i) {try {Thread.sleep(1000);} catc…

javascript --- 异步函数的顺序进行

假设我们希望某一组异步函数能一次进行,在不使用的任何工具的情况下,可能会编写出类似下面的代码: funcs[0](function() {funcs[1](function() {funcs[2](onComplete);}) });// 注:以上代码运行会出现的一些不方便: // 1.回调太深,不利于阅读..(100层嵌套...); // 2.不能使用循…

2021前端面试题

基础知识与素养 JS基本功训练与思考 程序设计的渗透与应用 业务技巧的积累与训练 生产力转换 项目的组织架构 转换专业人才的全面生产力 什么样的技术水平决定了你应该学习什么样的知识与技术&#xff0c;什么样的知识与技术水平决定了你到什么样的公司&#xff0c;到什么样的公…

JS的自定义事件(观察者模式)

1      var Event {2 on: function (eventName, callback) {3 console.log("eventName:"eventName)4 if (!this.handles) {5 Object.defineProperty(this, "handles", {6 …

glog日志库使用笔记

日志能方便地诊断程序原因、统计程序运行数据&#xff0c;是大型软件系统必不可少的组件之一。glog 是google的开源日志系统&#xff0c;相比较log4系列的日志系统&#xff0c;它更加轻巧灵活。 在Github上下载glog&#xff0c;解压后用CMake生成VS2017工程&#xff08;默认生成…

javascript --- 异步工作流的动态排队技术

很多情况下,使用async.series和async.paralle存在一个明显的问题,即: 1.其任务队列是静态的,在其调用前,一定要明确任务队列的数量,一旦明确了任务队列的数量,就不能改变. 2.倘如要同时并发读取上千个文件,使用async.paralle明显不可能(各线程抢资源,根本不够用),使用async.ser…

java中的内部类总结

内部类不是很好理解&#xff0c;但说白了其实也就是一个类中还包含着另外一个类 如同一个人是由大脑、肢体、器官等身体结果组成&#xff0c;而内部类相当于其中的某个器官之一&#xff0c;例如心脏&#xff1a;它也有自己的属性和行为&#xff08;血液、跳动&#xff09; 显然…

elementPlus关闭弹窗,页面原先滚动条消失

一开始以为是弹窗内容超过一屏引起&#xff0c;改为一屏内也不能解决。 打开控制台&#xff0c;发现弹窗后自动给body标签加上了类el-popup-parent–hidden&#xff0c;关闭后也没去除&#xff0c;因此手动删除该类。 document.getElementsByTagName(body)[0].className ;

在Windows下如何创建虚拟环境(默认情况下)

很多小伙伴平时在使用Python的时候&#xff0c;有的项目需要使用Python2来进行开发&#xff0c;有的项目则是需要Python3来进行开发。当不清楚怎么分开环境的时候&#xff0c;此时两个环境开始打架&#xff0c;彼此傻傻分不清楚。虚拟环境作为隔离的利器应运而生&#xff0c;其…

javascript --- 隐藏内部实现(最小暴露原则)

看下面的一个例子: function doSomething(a) {b a doSomethingElse( a * 2 );console.log( b * 3 ); }function doSomethingElse(a) {return a - 1; }var b;doSomething( 2 ) ; // 15上述代码中的doSomethingElse实际上应该是doSomething的"私有"部分,根据最小暴露…

selenium python 入门-元素定位

环境搭建 安装教程 http://www.testclass.net/selenium_python/install-selenium/ chrome浏览器 还需要下载chrome driver 把下载的chromedriver .exe放到chrome安装目录下的Application目录下和 python所在的安装目录下&#xff0c;比如我的目录是C:\Program Files (x86)\Goog…

ES5程序设计转ES6 笔记

课程链接 1. 立即执行函数 特点&#xff1a;执行结束&#xff0c;立即销毁&#xff1b;独立作用域执行符号&#xff08;&#xff09;只能跟在表达式后面&#xff0c;不能放在函数声明后分号可以写在前面/后面document为传入实参&#xff0c;doc为形参 ;(function(doc){...co…

DPDK helloworld 源码阅读

在 DPDK Programmers Guides 中的 EAL 一篇中有一个图可以很清晰地看到一个DPDK的应用程序的大致执行思路&#xff1a; 初始化检查CPU支持、微架构配置等完成后&#xff0c;执行main()函数。 第一步是 rte_eal_init()&#xff0c;核心初始化和启动。其中线程使用的是pthread库&…

javascript --- 作用域和闭包

执行环境: // 定义了变量或函数有权访问的其他数据,决定了它们各自的行为 // 每个执行环境都有一个变量对象与之对应,执行环境中所定义的所有变量和函数都保存在变量对象中 // 某个执行环境中的所有代码执行完毕后,该执行环境被销毁,保存在其中的所有变量和函数定义也随之销毁…

异步下载圆形进度条显示进度

圆形进度条参考链接即可&#xff1a;使用css3实现圆形进度条 需求点击下载后遮罩层显示下载进度&#xff1a; 1.圆形进度条参考以上链接&#xff0c;有点小瑕疵&#xff0c;可更改定位距离实现重合。 2.遮罩层&#xff1a; .lbOverlay{display: none;position: fixed;left: 0;…

javascript基本功

隐式类型转换 var a {_default: 0,toString: function () {return a._default} } if (a 1 && a 2 && a 3) {console.log(解) } 访问一个变量的时候进行拦截 var _default 0 Object.defineProperty(window, a, {get() {return _default} }) if (a 1 &am…

深信服笔试,抓兔子

*问题描述&#xff1a;抓兔子n个排成一排的洞&#xff0c;编号为1到n&#xff0c;兔子每天晚上会跳到相邻的一个洞里&#xff0c;小q每天只能白天检查其中的一个洞&#xff0c;小q会告诉你每天检查的洞&#xff0c;分析是否一定能抓到兔子示例&#xff1a;3个洞&#xff0c;第一…

es6 --- 模块

function foo(){var something cool;var another [1, 2, 3];function doSomething() {console.log( something );}function doAnother() {console.log( another.join( " ! " ) );} } // 是一个不明显的闭包,doSomething()和doAnother()保持了foo的内部作用域接下来…

Java之递归遍历目录,修改指定文件的指定内容

EditProperties.java 1 package PropertiesOperation.Edit;2 3 import java.io.File;4 5 /**6 * 替换指定Porpoerties文件中的指定内容7 * 三个参数&#xff1a;8 * filePath&#xff1a;存放properties文件的目录9 * srcStr&#xff1a;需要替换的字符串 10 * desStr&…