斜率优化 dp 总结
我们以一道例题引入:
洛谷 P2365 任务安排:
\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。
从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。
每个任务的费用是它的完成时刻乘以一个费用系数 \(C_i\)。请确定一个分组方案,使得总费用最小。
设 \(dp_i\) 为选取到第 \(i\) 个任务时的最大价值,枚举一个起点 \(j\) 分批可以得到:
\]
其中对于序列 \(S\),
\]
后面这个等号是求法,\(\rm sumS\) 是前缀和,也就是
\]
时间复杂度 \(O(n^2)\) .
Code:
#include<ctime>
#include<queue>
#include<stack>
#include<cmath>
#include<iterator>
#include<cctype>
#include<vector>
#include<map>
#include<set>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<bitset>
using namespace std;
const int N=5005;
int n,s,t[N],c[N],sumT[N],sumC[N],dp[N];
int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;i++)
{
scanf("%d%d",t+i,c+i);
sumT[i]=sumT[i-1]+t[i]; sumC[i]=sumC[i-1]+c[i];
} memset(dp,0x3f,sizeof dp); dp[0]=0;
for (int i=1;i<=n;i++)
for (int j=0;j<i;j++)
dp[i]=min(dp[i],dp[j]+sumT[i]*(sumC[i]-sumC[j])+s*(sumC[n]-sumC[j]));
printf("%d",dp[n]);
return 0;
}
这个 \(O(n^2)\) 的做法还是太慢了,过不了 \(3\times 10^5\) 的数据,考虑优化。
要优化,当然先对动态转移方程变形:
\]
此时令 \(\begin{cases}dp_j=y&\text{因变量}\\\text{sumT}_i+s=k&\text{斜率}\\\text{sumC}_j=x&\text{自变量}\\dp_i-\text{sumT}_i\text{sumC}_i-s\cdot\text{sumC}_n=b&\text{截距}\end{cases}\) 可以得到:
\]
发现这正好是一个直线方程,并且 \(x\) 是随着 \(j\) 改变而改变的。
别忘了我们的目标:使 \(dp_i\) 最小,要使 \(dp_i\) 最小,就要让 \(b\)(截距)最小(\(b\) 中除了 \(dp_i\) 以外的东西都是常量)。
我们可以在平面直角坐标系上点出下列点:\((dp_0,\text{sumC}_0),(dp_1,\text{sumC}_1),\cdots,(dp_{i-1},\text{sumC}_{i-1})\) 还有斜率 \(k\) 表示的直线:

P.S. 以下所有图的横坐标都是 \(\text{sumC}_j\),纵坐标都是 \(dp_j\) .
我们将 \(b\) 改变,直线将会滑动:

注意最小的 \(j\) 正好就是滑动时第一次遇到的点。
每次更新的时候后面都会插入点,注意到斜率 \(k\) 和插入点的横坐标都是 递增 的,所以我们可以将相邻两点的直线连上,然后把上面的点全部去掉(因为不可能会成为最小的 \(j\) 了):

发现绿线那里正好连成了一个下凸壳(凸包的定义:一个多边形是凸包当且仅当对于它的所有边满足所有点都在它所在的直线的一侧)。
现在我们怎么找遇到的第一个点呢?
注意到凸包相邻两点间的的斜率是递增的,手玩或者找规律可以得到答案就是 第一个斜率 \(>k\) 的点 所以我们可以二分。
当然,我们还有一种办法。
这个问题相当于在一个单调队列中找第一个大于 \(k\) 的点。
策略 \(1\):在查询的时候,可以把队头小于当前斜率的点全部删掉(因为不可能会参与答案了)
策略 \(2\):在插入的时候,将队尾所有不在凸包(不满足凸包性质)的点全部删掉(不要删掉插入进去的点)
不满足凸包性质的判断是纵坐标高于两点并且横坐标在两点之间,比如下图中,加入新点 \(N\),这使得 \(G\) 不满足性质,应当删去。

就按这写代码即可,注意条件:
- 策略 \(1\):\(\dfrac{dp_2-dp_1}{C_2-C_1}\le \text{sumT}_i+s\)
- 策略 \(2\):\(\dfrac{dp_{tail}-dp_{tail-1}}{C_{tail}-C_{tail-1}}\ge\dfrac{dp_{i}-dp_{tail}}{C_{i}-C_{tail}}\)(其中 \(tail\) 是队尾)
上面是按斜率式写的,应该很容易理解,但是代码里应该注意式子有除法要移项变成乘法减少误差。
Code:
#include<ctime>
#include<queue>
#include<stack>
#include<cmath>
#include<iterator>
#include<cctype>
#include<vector>
#include<map>
#include<set>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<bitset>
using namespace std;
const int N=3e5+5;
typedef long long ll; // 开 long long
int n,s,t[N],c[N];
ll sumT[N],sumC[N],dp[N],q[N];
int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;i++)
{
scanf("%d%d",t+i,c+i);
sumT[i]=sumT[i-1]+t[i]; sumC[i]=sumC[i-1]+c[i]; // 前缀和
} int head=0,tail=0; // q[0]=0; 这句是隐式的,不用写
for (int i=1;i<=n;i++)
{
while ((head<tail)&&(dp[q[head+1]]-dp[q[head]]<=(sumT[i]+s)*(sumC[q[head+1]]-sumC[q[head]]))) ++head; // 策略 1
int j=q[head];
dp[i]=dp[j]+sumT[i]*(sumC[i]-sumC[j])+s*(sumC[n]-sumC[j]); // 转移,此时的 j(也就是 q[head])已经是最小的 j 了,所以不用加 min 了
while ((head<tail)&&((dp[q[tail]]-dp[q[tail-1]])*(sumC[i]-sumC[q[tail]])>=
(dp[i]-dp[q[tail]]) *(sumC[q[tail]]-sumC[q[tail-1]]))) --tail; // 策略 2
q[++tail]=i; // 最后再入队(因为策略 2 里不能把插入的点丢出去)
}
printf("%lld",dp[n]);
return 0;
}
,
如果像 SDOI2012 任务安排 那样,\(T_i\) 是负数(时 间 倒 流),那么单调性就没了,就只能二分了 qwq
将原代码里的
while ((head<tail)&&(dp[q[head+1]]-dp[q[head]]<=(sumT[i]+s)*(sumC[q[head+1]]-sumC[q[head]]))) ++head;
int j=q[head];
换成
int l=head,r=tail;
while (l<r)
{
int mid=(l+r)>>1;
if (dp[q[mid+1]]-dp[q[mid]]>(sumT[i]+s)*(sumC[q[mid+1]]-sumC[q[mid]])) r=mid;
else l=mid+1;
} int j=q[r];
即可。
那么如果 \(C_i\) 是负数呢?
可以倒序 dp,设计一个状态转移方程,让 \(\text{sumT}_i\) 是横坐标,\(\text{sumC}_i\) 是斜率中的一项。仍然可以用单调队列维护凸壳,用二分法求出最优决策。
那么如果 \(T_i,C_i\) 都是负数呢?
可以考虑 cdq 分治或平衡树维护凸包,具体可以参考 NOI2007 货币兑换或者 OI-Wiki。
总结一下,斜率优化是解决形如
\]
其中 \(L(i),R(i)\) 是关于 \(i\) 的一次函数,\(S(i,j)\) 是关于 \(i,j\) 的多项式(可以有 \(i,j\) 的乘积项)。
(这好像也叫 1D/1D 型转移?)
还有一个特点:斜率优化的转移方程一般很复杂 qwq
习题:Codeforces 311B Cats Transport
斜率优化 dp 总结的更多相关文章
- bzoj-4518 4518: [Sdoi2016]征途(斜率优化dp)
题目链接: 4518: [Sdoi2016]征途 Description Pine开始了从S地到T地的征途. 从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站. Pine计划用m天到达T地 ...
- bzoj-1096 1096: [ZJOI2007]仓库建设(斜率优化dp)
题目链接: 1096: [ZJOI2007]仓库建设 Description L公司有N个工厂,由高到底分布在一座山上.如图所示,工厂1在山顶,工厂N在山脚.由于这座山处于高原内陆地区(干燥少雨),L ...
- [BZOJ3156]防御准备(斜率优化DP)
题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=3156 分析: 简单的斜率优化DP
- 【BZOJ-1096】仓库建设 斜率优化DP
1096: [ZJOI2007]仓库建设 Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3719 Solved: 1633[Submit][Stat ...
- BZOJ 1010: [HNOI2008]玩具装箱toy 斜率优化DP
1010: [HNOI2008]玩具装箱toy Description P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再 ...
- BZOJ 3156: 防御准备 斜率优化DP
3156: 防御准备 Description Input 第一行为一个整数N表示战线的总长度. 第二行N个整数,第i个整数表示在位置i放置守卫塔的花费Ai. Output 共一个整数,表示最小的战 ...
- HDU2829 Lawrence(斜率优化dp)
学了模板题之后上网搜下斜率优化dp的题目,然后就看到这道题,知道是斜率dp之后有思路就可以自己做不出来,要是不事先知道的话那就说不定了. 题意:给你n个数,一开始n个数相邻的数之间是被东西连着的,对于 ...
- HDU3507 Print Article(斜率优化dp)
前几天做多校,知道了这世界上存在dp的优化这样的说法,了解了四边形优化dp,所以今天顺带做一道典型的斜率优化,在百度打斜率优化dp,首先弹出来的就是下面这个网址:http://www.cnblogs. ...
- HDU 3507 Print Article(斜率优化DP)
题目链接 题意 : 一篇文章有n个单词,如果每行打印k个单词,那这行的花费是,问你怎么安排能够得到最小花费,输出最小花费. 思路 : 一开始想的简单了以为是背包,后来才知道是斜率优化DP,然后看了网上 ...
- 斜率优化dp(POJ1180 Uva1451)
学这个斜率优化dp却找到这个真心容易出错的题目,其中要从n倒过来到1的确实没有想到,另外斜率优化dp的算法一开始看网上各种大牛博客自以为懂了,最后才发现是错了. 不过觉得看那些博客中都是用文字来描述, ...
随机推荐
- PowerJob高级特效-容器部署完整教程
介绍 powerjob提供了容器功能,用来做一些灵活的任务处理.这里容器为 JVM 级容器,而不是操作系统级容器(Docker).(至于为什么取"容器"这个有歧义的名字是因为作者没 ...
- css,html实现元素超出部分省略号
.line-1 { height: 25px; width: 200px; overflow: hidden; text-overflow: ellipsis; display: -webkit-bo ...
- AJAX——POST请求
POST.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...
- 关于『Markdown』:第一弹
关于『Markdown』:第一弹 建议缩放90%食用 声明: 在我之前已有数位大佬发布 "Markdown" 的语法知识点, 在此, 仅整理归类以及补缺, 方便阅读. 感谢 C20 ...
- 关闭StackExchange等平台的privacy收集窗口
技术背景 当我们打开一个StackExchange页面的时候,经常会出现一个很大的privacy收集窗口,而且不管怎么点都关闭不了,比如像下图这样: 如果屏幕足够大,影响可能也不是很大,但是关键是对于 ...
- LoRa无线传输技术与LoRaWAN无线模块的区别
有不少人分不清LoRaWAN无线模块与LoRa网关无线传输技术到底有什么区别,他们在物联网领域的应用到底是什么样的. LoRaWAN指的是MAC层的组网协议,而LoRa是一个物理层的协议.虽然现有的L ...
- Redis的使用(二)
一.redis简单应用 其实在写这个redis专题时我想了很久,我觉得redis没什么好说的,因为现在是个人都会用redis,但是我在写netty专题时发现,netty里面很多东西和概念有很多跟red ...
- MongoDB学习总览
第1部分: MongoDB入门(第1~6章) 该部分介绍MongoDB的基本概念及入门知识. 通过该部分的学习,读者可对MongoDB自身的技术全貌形成一定的认识. 第2部分: MongoDB微服务开 ...
- Mybatis架构原理(二)-二级缓存源码剖析
Mybatis架构原理(二)-二级缓存源码剖析 二级缓存构建在一级缓存之上,在收到查询请求时,Mybatis首先会查询二级缓存,若二级缓存没有命中,再去查询一级缓存,一级缓存没有,在查询数据库; 二级 ...
- 【由浅入深_打牢基础】HOST头攻击
[由浅入深_打牢基础]HOST头攻击 前几天一直准备别的事情,然后用了2/3天时间去挖了补天某厂的SRC,还是太菜了,最后提交了一个低危(还没出结果,还有点敏感信息泄露,感觉略鸡肋也没交),不过偶然发 ...