斜率优化 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的算法一开始看网上各种大牛博客自以为懂了,最后才发现是错了. 不过觉得看那些博客中都是用文字来描述, ...
随机推荐
- k8s中应用GlusterFS类型StorageClass
GlusterFS在Kubernetes中的应用 GlusterFS服务简介 GlusterFS是一个可扩展,分布式文件系统,集成来自多台服务器上的磁盘存储资源到单一全局命名空间,以提供共享文件存储. ...
- Git生成ssh keys加密算法ed25519
1:桌面右击后出现Git push hehe点击进入直接输入以下命令 ①:ssh-keygen -t ed25519 -f my_github_ed25519 -C "xxxxx" ...
- RealEvo-IDE安装
双击"InstallWizard.exe"启动安装程序 点击"Install RealEvo-IDE"启动 RealEvo-IDE 安装程序 选择"下 ...
- TypeError: this.getOptions is not a function
我在vue ui界面中安装版本依赖包后报这个错误 less-loader/sass-loader安装的版本过高 解决办法 删除原有的版本依赖包,安装更低版本的依赖包. 如 @6.0.1为选择安装的版本 ...
- ACM 刷题记录
HDU Multi-University Training Contest 题目来源 题目 知识点 时间复杂度 完成情况 2019 Contest8 A Acesrc and Cube Hyperne ...
- Druid数据库连接池使用体验
写在前面 在实际工作中我们我们使用较多的则是Spring默认的HikariDataSource数据库连接池,但是它无法提供可视化监控SQL这一能力,而这在很多场景下往往又是我们需要的功能,因此今天来学 ...
- Go微服务框架go-kratos实战04:kratos中服务注册和服务发现的使用
一.简介 关于服务注册和服务发现介绍,我前面的文章有介绍过 - 服务注册和发现的文章. 作为服务中心的软件有很多,比如 etcd,consul,nacos,zookeeper 等都可以作为服务中心. ...
- 配置中心的设计-nacos vs apollo
简介 前面我们分析了携程的 apollo(见 详解apollo的设计与使用),现在再来看看阿里的 nacos. 和 apollo 一样,nacos 也是一款配置中心,同样可以实现配置的集中管理.分环境 ...
- JAVA - 如果hashMap的key是一个自定义的类,怎么办?
JAVA - 如果hashMap的key是一个自定义的类,怎么办? 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals().
- EnvironmentLocationNotFound: Not a conda environment: C:\Program Files\Anaconda3
可参考:https://blog.csdn.net/dscn15848078969/article/details/114743744