斜率优化dp 的简单入门
不想写什么详细的讲解了...而且也觉得自己很难写过某大佬(大米饼),于是建议把他的 blog 先看一遍,然后自己加了几道题目以及解析...顺便建议看看算法竞赛(蓝皮书)的 0x5A 斜率优化(P294) 部分
这是——大米饼大佬
看完了大米饼同志对斜率优化的介绍,下面我来稍微讲讲对斜率优化dp 的理解
前置知识
- 单调队列(栈)
- 平面直角坐标系
- 直线解析式
- 等式处理
- dp状态设计
- balabala......
理解
其实斜率优化 dp 的原理很简单:
- 根据题目(斜率优化 dp 的题目一般都很裸)的 题意 以及 数据范围 先根据设计一个状态出来,当做 dp 数组。
- 根据已经设计好的 dp 状态设计一个状态转移方程,如果设计失败则跳回第一步,重新设计 dp 状态。
- 在第二步完成后,我们一般可以得到这样的一个式子:$ Fi = Fj +$ $(一个表达式)$
- 根据设计的状态做一些乱七八糟的东西(比如说将式子展开啦,移项啦...这些大米饼讲的应该挺清楚的吧)。
- 在第四步中,我们在将式子展开后,把下标中同时带有 i、j 的项(注意这里是项,如 Si*Sj)移到等式的左边。
- 然后我们就可以得到一个 $b + k x = y$ 形态的一般式(大米饼是这么叫的),其中等式左边下标带有 i 的变量(注意这里是变量,如Si)作为 k ,下标带有 j 的变量作为 x ,等式右边下标带有 j 的变量作为 y ,至于等式右边那些下标只带有 i 的变量我们不去管它,因为 i 在当前对于 Fi 的计算中相当于一个常量,对结果的影响一定。
- 然后我们发现这时候我们就可以得到点 (x, y) 了,那么我们就基本上已经完成了这道 斜率优化 dp 题了。
- 那么我们是要根据题意让 Fi (也就是一般式中的 b )取到最小(大)值,那么我们的任务也就转化成了过一个点,画一条斜率为 k 的射线,令其与 y 轴的截距(与 y 轴的交点和原点的距离叫做截距)最小(大)。
做完这些工作之后,你把所有的点画到平面直角坐标系上,就可以看到这样的一条折线(假设我们现在求的是 Fi 的最小值):
没错,这就是可能有用的点形成的像凸包一样的东西(凸壳)。那为什么这些点形成的折线一定是向下凸起的呢?
我们可以想想一下,如果折线内凹,那么引起内凹的那个点可能成为有用点吗?不可能,因为经过该点的斜率为 k 的直线与 y 轴的截距必然不会比它旁边两个点的截距要小
那么我们在来考虑一下,如果我们要求的是 Fi 的最大值呢?那么我们只要让折线向上凸起就好了(维护上凸性)。
对了对了!还有一点蛮重要的。那就是斜率优化可行性判定的标准(我自己口胡的): 我们在上面的步骤中会处理出一个一般式中的 k 吗?那个 k 要满足单调性,
不然是没办法用双端队列(单调队列)维护的。
然后这里有一些题目
题目
洛谷P4072 [SDOI2016]征途
分析
首先你在做这题之前最好已经做过了 洛谷P3648 [APIO2014]序列分割 这道题(上面的 T6)。
这道题你只要一直推推推把式子推出来,然后发现 $ans = m * \sum_{i=1}^{m}a[i]^{2} - sum[n]^{2}$ ,
那么你就能知道我们要求的是 $min{\sum_{i=1}^{m}a[i]^{2}}$ (这就是序列分割啊),最后答案处理一下就好了。
代码
//by Judge
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int M=;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
ll n,m,sum[M],g[M],f[M],q[M],head,tail;
inline double Rate(ll i,ll j){
if(sum[i]==sum[j]) return -1e9;
return (g[j]-g[i]+sum[j]*sum[j]-sum[i]*sum[i])/(1.0*sum[j]-sum[i]);
}
signed main(){
n=read(),m=read();
for(int i=;i<=n;++i)
sum[i]=sum[i-]+read(),g[i]=sum[i]*sum[i];
for(int k=,i;k<=m;++k){
head=tail=;
for(i=;i<=n;++i){
while(head<tail && Rate(q[head+],q[head])<=*sum[i]) ++head;
f[i]=g[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);
while(head<tail && Rate(q[tail-],q[tail])>=Rate(q[tail],i)) --tail; q[++tail]=i;
} swap(f,g);
} printf("%lld\n",g[n]*m-sum[n]*sum[n]); return ;
}
洛谷P4360 [CEOI2004]锯木厂选址
分析
首先这道题其实与仓库建设类似,同时(可以算是)综合了序列分割...但是数据的处理有点麻烦(甚至还有点坑,比如 d 和 w 搞反了然后样例里面 d 、w 读入雷同...)
这题偷懒一点就是 抄 综合 一下序列分割这道题,将它作为模板,K 设成 3 ,n 要加一,然后常规做就好了。推式子也不是很麻烦,和仓库建设一样的套路
代码
//by Judge
#include<iostream>
#include<cstdio>
#define int long long
#define ll long long
using namespace std;
const int M=5e4+;
#ifdef ONLINE_JUDGE
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
ll n,K,x[M],w[M],s[M],g[M],f[M],q[M],head,tail;
inline double Rate(ll i,ll j){
if(w[i]==w[j]) return -1e18;
return (g[j]-g[i]+s[j]-s[i])/(1.0*w[j]-w[i]);
}
int las[M];
signed main(){
n=read()+;
for(int i=;i<=n;++i)
(i<n)&&(w[i]=read(),x[i+]=x[i]+read()),
s[i]=s[i-]+x[i]*w[i],w[i]+=w[i-],g[i]=1e18;
for(int k=,i;k<=;++k){
head=tail=;
for(i=;i<=n;++i){
while(head<tail && Rate(q[head+],q[head])<=x[i]) ++head;
f[i]=g[q[head]]+x[i]*(w[i-]-w[q[head]])-s[i-]+s[q[head]];
while(head<tail && Rate(q[tail-],q[tail])>=Rate(q[tail],i)) --tail; q[++tail]=i;
} swap(f,g);
} printf("%lld\n",g[n]); return ;
}
其他题...咳咳(来自刷题慢者的尴尬...做一道题都要在黑板上墨迹个半天QwQ)
洛谷P2365 任务安排
分析
这道题...其实蛮(不)简单的啦。这题你要消除后效性才能做(其实这是一道经典题目,算法竞赛上都有)。
一开始我们让 $1~n$ 这一整段为一个区间,现在我们考虑当前将$ 1 ~ i $这一区间分为一段,
那么后面所有的任务(以及 $1 ~ i$ 这段区间)必定会多加上 S 时间的代价(即 $S * (C_{n}-C_{1})$,其中 C 为 c 数组的前缀和),而后面区间的分割并不会对前面的分割造成影响。
同理, i 之后的区间我们也可以按照这个思路分下去,以消除后效性。那么我们现在就可以列出状态转移方程:$ Fi = Fj + (Cj-Ci)*xi + (Cn-Cj)*S $ 然后展开式子移项后我们就可以斜率优化了。
代码
//by Judge
#include<iostream>
#include<cstdio>
#define mid (l+r>>1)
#define ll long long
using namespace std;
const int M=1e6+;
#ifdef online_judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
ll n,S,head,tail,q[M],t[M],c[M],f[M];
inline double X(int i){ return c[i]; }
inline double Y(int i){ return f[i]-c[i]*S; }
inline double Rate(int i,int j){ return (Y(j)-Y(i))/(X(j)-X(i)); }
signed main(){
n=read(),S=read();
for(int i=;i<=n;++i) t[i]=t[i-]+read(),c[i]=c[i-]+read();
for(int i=;i<=n;++i){
while(head<tail && Rate(q[head+],q[head])<=t[i]) ++head;
f[i]=f[q[head]]+(c[n]-c[q[head]])*S+(c[i]-c[q[head]])*t[i];
while(head<tail && Rate(q[tail],q[tail-])>=Rate(i,q[tail])) --tail; q[++tail]=i;
} printf("%lld\n",f[n]); return ;
}
洛谷CF311B Cats Transport
分析
没什么好分析的和上面一样公式套取就好了(套个鬼哦)。
咳咳。首先你要分析怎么把这道题硬设计出 dp 状态。那么我们来看看,其实路程对于问题的影响并不显得有多么重要,于是我们可以考虑消除这个影响。
如何消除?题目中说,饲养员(说好的铲屎官...)到达猫的位置所需时间就是距离 $X_{i}$,那么其实猫在 $T_{i}$ 的时间开始等待,而饲养员出发时间对于我们要求的答案是没有影响的(何况题目中说了饲养员出发时间可以为负数),
那么我们可以让猫开始等待的时间 $T_{i}$ 减去路程的影响 $X_{i}$ (感性理解一下),然后我们再对减完 $X_{i}$ 的 $T_{i}$ 排一下序就好了。
这里如何解释?emmm...思考一下,一个饲养员出发必然是会接回所有正在等待的猫对吧(猫不可能给下一个人接,那样不会更优,而上一个人能接回去早接回去了)
咳咳...那么这个饲养员能接到哪些猫呢?当然是 $T-X$(等待开始时间减去路程) 小于等于饲养员出发时间的所有猫咯!于是解释完毕。
排完序后,可以看出我们要接到第 i 只猫的话,它前面的猫我们都可以接到(因为在这只猫之前的猫早就开始等待了)。
于是这道题就变成了分割序列...(切 p 刀,但是注意这里是最多切 p 刀,不一定切完,有的饲养员可以不动的嘛)。
咳咳,但是转移方程是不一样的:$ F_{i} = F_{j} + (T_{i}-T_{i-1}) * (i-j) - (T_{i}-T_{j}) $ (T 是上文中 $T-X$ 的前缀和数组)
那么这个表达式原来的样子是:$ F_{i} = MIN{ F_{j} + \sum_{k=j}^{i} ( t_{i}-t_{k} ) }$ (这里的 t 是上文中的 $T-X$ 数组)
然后我们就非常愉快的 ctrl+C 、 ctrl+V 将之前打好的序列分割板子弄了下来开始了新一轮的斜率优化。
代码
//by Judge
#include<algorithm>
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int M=1e5+;
const ll inf=1e16+;
#ifdef ONLINE_JUDGE
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
ll n,m,p,ans=inf,d[M],a[M],g[M],f[M],q[M],head,tail;
inline double Y(int i){ return g[i]+a[i]; }
inline double X(int i){ return i; }
inline double Rate(ll i,ll j){ return (Y(j)-Y(i))/(X(j)-X(i)); }
signed main(){
n=read(),m=read(),p=read();
for(int i=;i<=n;++i) d[i]=d[i-]+read();
for(int i=,x,y;i<=m;++i) x=read(),y=read(),a[i]=y-d[x],g[i]=inf;
sort(a+,a+m+); for(int i=;i<=m;++i) a[i]+=a[i-];
for(int k=,i;k<=p;++k){
head=tail=;
for(i=;i<=m;++i){
while(head<tail && Rate(q[head+],q[head])<=a[i]-a[i-]) ++head;
f[i]=g[q[head]]+(a[i]-a[i-])*(i-q[head])-a[i]+a[q[head]];
while(head<tail && Rate(q[tail-],q[tail])>=Rate(q[tail],i)) --tail; q[++tail]=i;
} swap(f,g); ans=min(ans,g[m]);
} printf("%lld\n",ans); return ;
}
BinamotoOJ P4709: [Jsoi2011]柠檬
分析
这题就是斜率优化裸题啊! 这道题非常值得一做,因为它深刻的告诉了我们,斜率优化可以用单调栈维护!
首先这题就是让我们吧一个序列分成若干份,然后根据公式计算最大值(注意是最大值,维护折线上凸性)
我们可以非常轻松的看出我们要取的一段区间的左右端点必然是相同的颜色,且我们选择的颜色就是左右端点的颜色。
(我们每一段只能选一种颜色,那么如果左右端点不同,我们完全可以将与选择的颜色不同的那一端隔离出来分到另一段区间里,那样更优)
于是我们在读入时维护一个 las 指针,指向当前颜色上一次出现的位置,同时记录每个点之前与该点颜色相同的点有多少个(用 S 数组记录)。
然后我们将所有颜色第一次出现时的位置压入单调栈,接着就可以开始 dp 了。
那么 dp 转移式就是 : $$ f[i] = f[j-1] + (s[i]-s[j]+1)*a[i] $$ (其中 i 、j 位置的贝壳颜色相同)
斜率式就是: $$ f[i] + 2*a[i]*s[i]*s[j] = f[j-1] + a[i]*s[j]^{2} - 2*a[i]*s[j] + 2*a[i]*s[i]+a[i]*s[i]^{2}+a[i] $$
$$ X(i)= s[j] , K=2*a[i]*s[i] $$
$$ Y(i)= f[j-1] + a[i]*s[j]^{2} - 2*a[i]*s[j] $$
代码
//by Judge
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
using namespace std;
const int M=1e5+;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
ll n,ans,las[M],a[M],s[M],f[M],top[M]; vector<int> q[M];
inline long double X(int i){ return s[i]; }
inline long double Y(int i){ return f[i-]+a[i]*s[i]*(s[i]-); }
inline long double Rate(ll i,ll j){ return (Y(j)-Y(i))/(X(j)-X(i)); }
signed main(){
n=read(); ll p,x,y;
for(int i=;i<=n;++i) a[i]=read(),s[i]=s[las[a[i]]]+,las[a[i]]=i;;
for(int i=;i<=n;++i) if(las[a[i]]) q[a[i]].push_back(i),las[a[i]]=;
for(int i=;i<=n;++i){ p=a[i];
while(top[p]> && Rate(q[p][top[p]-],q[p][top[p]])<=Rate(q[p][top[p]],i)) --top[p],q[p].pop_back();
++top[p],q[p].push_back(i);
while(top[p]> && Rate(q[p][top[p]-],q[p][top[p]])<=*p*s[i]) --top[p],q[p].pop_back();
f[i]=f[q[p][top[p]]-]+(s[i]-s[q[p][top[p]]]+)*(s[i]-s[q[p][top[p]]]+)*p;
} printf("%lld\n",f[n]); return ;
}
斜率优化dp 的简单入门的更多相关文章
- 蒟蒻关于斜率优化DP简单的总结
斜率优化DP 题外话 考试的时候被这个玩意弄得瑟瑟发抖 大概是yybGG的Day4 小蒟蒻表示根本不会做..... 然后自己默默地搞了一下斜率优化 这里算是开始吗?? 其实我讲的会非常非常非常简单,, ...
- bzoj1010[HNOI2008]玩具装箱toy 斜率优化dp
1010: [HNOI2008]玩具装箱toy Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 11893 Solved: 5061[Submit][S ...
- 2018.09.05 任务安排(斜率优化dp)
描述 这道题目说的是,给出了n项必须按照顺序完成的任务,每项任务有它需要占用机器的时间和价值.现在我们有一台机器可以使用,它每次可以完成一批任务,完成这批任务所需的时间为一个启动机器的时间S加上所有任 ...
- 【转】斜率优化DP和四边形不等式优化DP整理
(自己的理解:首先考虑单调队列,不行时考虑斜率,再不行就考虑不等式什么的东西) 当dp的状态转移方程dp[i]的状态i需要从前面(0~i-1)个状态找出最优子决策做转移时 我们常常需要双重循环 (一重 ...
- 『摆渡车 斜率优化dp及总结』
摆渡车的题解我已经写过一遍了,在这里,这次主要从斜率优化的角度讲一下摆渡车,并总结一下斜率优化会出现的一些奇奇怪怪的错误. 摆渡车 Description 有 n 名同学要乘坐摆渡车从人大附中前往人民 ...
- [BZOJ3156]防御准备(斜率优化DP)
题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3156 分析: 简单的斜率优化DP
- HDU 3507 Print Article(斜率优化DP)
题目链接 题意 : 一篇文章有n个单词,如果每行打印k个单词,那这行的花费是,问你怎么安排能够得到最小花费,输出最小花费. 思路 : 一开始想的简单了以为是背包,后来才知道是斜率优化DP,然后看了网上 ...
- 2018.09.10 bzoj1597: [Usaco2008 Mar]土地购买(斜率优化dp)
传送门 终究还是通宵了啊... 这是一道简单的斜率优化dp. 先对所有土地排序,显然如果有严格小于的两块土地不用考虑小的一块. 于是剩下的土地有一条边单增,另外一条单减. 我们假设a[i]是单减的,b ...
- 2018.08.28 洛谷P4360 [CEOI2004]锯木厂选址(斜率优化dp)
传送门 一道斜率优化dp入门题. 是这样的没错... 我们用dis[i]表示i到第三个锯木厂的距离,sum[i]表示前i棵树的总重量,w[i]为第i棵树的重量,于是发现如果令第一个锯木厂地址为i,第二 ...
随机推荐
- iis+nginx实现负载均衡
简要说明:nginx的简介自行百度. 目的:把用户的请求分到各个服务器减轻压力.nginx把监听的端口的请求平均转到布署了网站的服务器. 一.windows上安装nginx 1.官网下载windows ...
- SpringCloud(5)路由网关Spring Cloud Zuul
一个简单的微服务系统如下图: 1.为什么需要Zuul Zuul很容易实现 负载均衡.智能路由 和 熔断器,可以做身份认证和权限认证,可以实现监控,在高流量状态下,对服务进行降级. 2.路由网关 继续前 ...
- 这段代码,c 1秒,java 9秒,c# 14秒,而python。。。
哎,不得不说最近见得键盘侠客太多了,做程序员没两天总是喜欢上嘴唇触天,下嘴唇碰地的吹. 自己分明都没用过几门语言,就对各门语言评头论足说三道四,这么语言多好那门语言有多烂. 可能是随着时间也变得没那么 ...
- springboot项目利用Swagger2生成在线接口文档
Swagger简介. Swagger2是一款restful接口文档在线生成和在线调试工具.很多项目团队利用Swagger自动生成接口文档,保证接口文档和代码同步更新.在线调试.简单地说,你可以利用这个 ...
- 572. Subtree of Another Tree(easy)
Given two non-empty binary trees s and t, check whether tree t has exactly the same structure and no ...
- c++学习之初话 函数指针和函数对象 的因缘
函数指针可以方便我们调用函数,但采用函数对象,更能体现c++面向对象的程序特性. 函数对象的本质:()运算符的重载.我们通过一段代码来感受函数指针和函数对象的使用: int AddFunc(int a ...
- 百度地图IP定位,点击地图添加marker
<html> <head> <meta http-equiv="Content-Type" content="text/html; char ...
- [转帖]Sqlserver BCP 的用法
SQL Server中bcp命令的用法以及数据批量导入导出 http://www.cnblogs.com/xwdreamer/archive/2012/08/22/2651180.html 我这边使用 ...
- JPA 连表查询
A表和B表 @Entity @Table(name = "A", schema = "kps", catalog = "kps") @Dyn ...
- python学习日记(初识面向对象)
面向过程 VS 面向对象 面向过程 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行.为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统 ...