算法

把一些 dp 的转移方程拆一拆,移一移,能拆成 \(y=kx+b\) 的形式(其中 \(k,b\) 只与当前的 \(i\) 有关,\(x,y\) 只与 \(j\) 有关,\(b\) 包含 \(f_i\))。

然后我们用单调队列维护一些点形成的凸包,每次找一个最优的点。

详见例题任务安排。

例题

任务安排

题意

有 \(N\) 个任务,每个任务都有一个所需时间 \(T_i\) 和费用 \(C_i\)。

现在要求你把这些任务分成若干批,每批任务所需费用为机器启动时间 \(S\) 加上这批任务的总时间再乘上这批任务的总费用。

求最小费用。

\(1 \le n \le 3 \times 10^5\),\(1 \le s \le 2^8\),\(0\le T_i \le 2^8\),\(0 \le C_i \le 2^8\)。

思路

转移方程为:

\[f_i=\min_{1\le j\le i}\{f_j+SumT_i*(SumC_i-SumC_j)+S*(SumC_n-SumC_j)\}\\
\]
咋算的?

设 \(f_i\) 表示把前 \(i\) 个任务划分成了若干段的最小费用,那么我们要找到一个 \(j\),使 \(f_j\) 加上 \((j+1,i)\) 这些任务的费用的总费用最小。

其中,\(SumT_i*(SumC_i-SumC_j)\) 是这些任务执行完的总费用,\(S*(SumC_n-SumC_j)\) 是这个启动时间对之后的所用任务产生的影响

拆一下可得:

\[f_i=\min_{1\le j\le i}\{f_j+SumT_i*SumC_i-SumT_i*SumC_j+S*SumC_n-S*SumC_j\}\\
\]

原式去掉 \(\min\) 得:

\[f_j=SumT_i*SumC_j-SumT_i*SumC_i-S*SumC_n+S*SumC_j+f_i\\
\]

进一步变形为:

\[f_j=(SumT_i+S)*SumC_j-SumT_i*SumC_i-S*SumC_n+f_i\\
\]

此时令:

\[\begin{cases}
x=SumC_j\\
y=f_j\\
k=(SumT_i+S)\\
b=f_i-SumT_i*SumC_i-S*SumC_n\\
\end{cases}
\]

则原转移方程就变成了一个直线方程 \(y=kx+b\)。

我们要让 \(f_i\) 尽量小,就是让截距 \(b\) 尽量小.

那么画一个横坐标 \(SumC_j\),纵坐标 \(f_j\) 的坐标系。

我们把 \((SumC_j,f_j)\ (1\le j<i)\) 这些点都放到坐标系上。

然后我们把直线 \(y=kx+b\) 放上去。

再往上移移移移移,看能碰到那个点,那个点就是我们想要的使 \(f_i\) 最小的点。

可以发现,有可能被碰上的点在一个下凸包上。

那么我们只要用单调队列维护这个相邻两点之间斜率递增的下凸包就可以了。

每加入一个点,都要保证这些点的斜率是递增的,即 \(\dfrac{f_{tail}-f_{tail-1}}{SumC_{tail}-SumC_{tail-1}}\ge\dfrac{f_{i}-f_{tail}}{Sum_{i}-SumC_{tail}}\)。

对于这道题,\(SumT\) 是递增的,也就是说斜率是递增的。

那么我们每次可以把所有在队头的斜率比当前斜率小的点给弹出去,找 \(f_j\) 时直接找队头。这样可以大大减少时间。

时间复杂度是 \(O(n)\)。

一定不要 像我一样 在移项的时候推错式子!!!

代码

点击查看代码
namespace DP{
inline ll x(ll i){return sc[i];}
inline ll y(ll i){return f[i];}
inline ll k(ll i){return st[i]+s;}
inline ll b(ll i){return f[i]-st[i]*sc[i]-s*sc[n];}
void Dp(){
ll q[N]={0},hd=1,tl=1;
memset(q,0,sizeof(q));
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
while(hd<tl&&(y(h(1))-y(h(0)))<=k(i)*(x(h(1))-x(h(0))))
++hd;
ll j=h(0);
f[i]=y(j)-k(i)*x(j)-b(i);
f[i]=f[j]+st[i]*(sc[i]-sc[j])+s*(sc[n]-sc[j]);
while(hd<tl&&(x(t(0))-x(t(1)))*(y(i)-y(t(0)))<=(x(i)-x(t(0)))*(y(t(0))-y(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}

[SDOI2012]任务安排

题意

题意和上一个任务安排一样。

\(1 \le n \le 3 \times 10^5\),\(1 \le s \le 2^8\),$ \left| T_i \right| \le 2^8$,\(0 \le C_i \le 2^8\)。

思路

和上一个任务安排相比,这里的 \(T_i\) 可以为负数。

也就是说,此时我们的斜率 \(SumT_i+S\) 不再具有单调性,那么我们就不能把队头的元素弹出去了,而是用二分找最优点。

时间复杂度是 \(O(nlogn)\)。

代码

点击查看代码
namespace DP{
ll q[N]={0},hd=1,tl=1;
inline ll x(ll i){return sc[i];}
inline ll y(ll i){return f[i];}
inline ll k(ll i){return st[i]+s;}
inline ll b(ll i){return f[i]-st[i]*sc[i]-s*sc[n];}
inline ll fd(ll i){
ll l=hd,r=tl;
while(l<r){
ll mid=(l+r)>>1;
if(y(q[mid+1])-y(q[mid])<=k(i)*(x(q[mid+1])-x(q[mid])))
l=mid+1;
else r=mid;
}
return l;
}
void Dp(){
memset(q,0,sizeof(q));
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
ll j=q[fd(i)];
f[i]=y(j)-k(i)*x(j)-b(i);
while(hd<tl&&(x(t(0))-x(t(1)))*(y(i)-y(t(0)))<=(x(i)-x(t(0)))*(y(t(0))-y(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}

任务安排 再改

题意

题意和上一个任务安排一样。

\(1 \le n \le 3 \times 10^5\),\(1 \le s \le 2^8\),$ \left| T_i \right| , \left| C_i \right| \le 2^8$。

思路

要用到平衡树或 CDQ 分治维护凸包。

但是作者还没学,所以它咕咕咕了。

练习题

[HNOI2008]玩具装箱

思路

随便推一推:
\[f_i=\min_{1\le j<i}\{f_j+(i-j-1+Sum_i-Sum_j-L)^2\}
\]
\[f_i=\min_{1\le j<i}\{f_j+[(i+Sum_i-1-L)-(j+Sum_j)]^2\}
\]
\[f_i=\min_{1\le j<i}\{f_j+(i+Sum_i-1-L)^2-2(i+Sum_i-1-L)(j+Sum_j)+(j+Sum_j)^2\}
\]
\[f_i=\min_{1\le j<i}\{f_j+(j+Sum_j)^2-2(i+Sum_i-1-L)(j+Sum_j)+(i+Sum_i-1-L)^2\}
\]
\[f_j+(j+Sum_j)^2=2(i+Sum_i-1-L)(j+Sum_j)+f_i-(i+Sum_i-1-L)^2
\]

此时显然:

\[\begin{cases}
x=j+Sum_j\\
y=f_j+(j+Sum_j)^2\\
k=2(i+Sum_i-1-L)\\
b=f_i-(i+Sum_i-1-L)^2\\
\end{cases}
\]

然后就可以优化了。

代码

点击查看代码
namespace DP{
ll q[N]={0},hd=1,tl=1;
inline ll x(ll i){return i+sum[i];}
inline ll y(ll i){return f[i]+(i+sum[i])*(i+sum[i]);}
inline ll k(ll i){return 2*(i+sum[i]-1-L);}
inline ll b(ll i){return f[i]-(i+sum[i]-1-L)*(i+sum[i]-1-L);}
void Dp(){
memset(q,0,sizeof(q));
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
while(hd<tl&&(y(h(1))-y(h(0)))<=k(i)*(x(h(1))-x(h(0))))
++hd;
ll j=h(0);
f[i]=y(j)-k(i)*x(j)-b(i);
while(hd<tl&&(y(t(0))-y(t(1)))*(x(i)-x(t(0)))>(y(i)-y(t(0)))*(x(t(0))-x(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}

[APIO2010]特别行动队

思路

随便推一推:
\[f_i=\max_{1\le j<i}\{f_j+a(Sum_i-Sum_j)^2+b(Sum_i-Sum_j)+c\}
\]
\[f_i=\max_{1\le j<i}\{f_j+a[(Sum_i)^2-2Sum_iSum_j+(Sum_j)^2]+b(Sum_i-Sum_j)+c\}
\]
\[f_i=\max_{1\le j<i}\{f_j+a(Sum_i)^2-2a\times Sum_iSum_j+a(Sum_j)^2+b\times Sum_i-b\times Sum_j+c\}
\]
\[f_j+a(Sum_j)^2-b\times Sum_j=(2aSum_i)\times Sum_j+(f_i-a(Sum_i)^2-b\times Sum_i-c)
\]

此时显然:

\[\begin{cases}
x=Sum_j\\
y=f_j+a(Sum_j)^2-b\times Sum_j\\
k=2aSum_i\\
b=f_i-a(Sum_i)^2-b\times Sum_i-c\\
\end{cases}
\]

然后就可以优化了。

注意:此时求的是最大值,也就是说维护的是一个上凸包。此时只要把下凸包往上翻一下,改一改大于号小于号就好了。

差不多长这样:

代码

点击查看代码
namespace DP{
ll q[N]={0},hd=1,tl=1;
inline ll X(ll i){return sum[i];}
inline ll Y(ll i){return f[i]+a*sum[i]*sum[i]-b*sum[i];}
inline ll K(ll i){return 2*a*sum[i];}
inline ll B(ll i){return f[i]-a*sum[i]*sum[i]-b*sum[i]-c;}
void Dp(){
memset(q,0,sizeof(q));
f[0]=0;
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
while(hd<tl&&(Y(h(1))-Y(h(0)))>=K(i)*(X(h(1))-X(h(0))))
++hd;
ll j=h(0);
f[i]=Y(j)-K(i)*X(j)-B(i);
while(hd<tl&&(Y(t(0))-Y(t(1)))*(X(i)-X(t(0)))<(Y(i)-Y(t(0)))*(X(t(0))-X(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}

[ZJOI2007]仓库建设

思路

随便推一推:
\[f_i=\min_{1\le j<i}\{f_j+\sum_{k=j+1}^{i}\{p_{k}*(x_{i}-x_k)\}+c_i{}\}
\]
\[f_i=\min_{1\le j<i}\{f_j+\sum_{k=j+1}^{i}\{p_{k}*x_{i}-p_{k}*x_k\}+c_i{}\}
\]
\[f_i=\min_{1\le j<i}\{f_j+x_{i}*SumP_i-x_{i}*SumP_j-SumPX_i+SumPX_j+c_{i}\}
\]
\[f_j+SumPX_j=x_{i}*SumP_j+(f_i-x_{i}*SumP_i+SumPX_i-c_{i})
\]

此时显然:

\[\begin{cases}
x=SumP_j\\
y=f_j+SumPX_j\\
k=x_{i}\\
b=f_i-x_{i}*SumP_i+SumPX_i-c_{i}\\
\end{cases}
\]

然后就可以优化了。

但是这道题还有一个坑点:\(0\leq x_i,p_i,c_i<2^{31}\)。

也就是说有的山没有货物,有时我们可能不必在山脚建仓库,而是在一个之后的工厂都没有货物的工厂建仓库!

解决办法是加上下面的代码:

for_(i,n,1){
ans=min(ans,f[i]);
if(p[i])break;
}

代码

点击查看代码
namespace DP{
ll q[N]={0},hd=1,tl=1;
inline ll X(ll i){return sp[i];}
inline ll Y(ll i){return f[i]+spx[i];}
inline ll K(ll i){return x[i];}
inline ll B(ll i){return f[i]-x[i]*sp[i]+spx[i]-c[i];}
void Dp(){
memset(q,0,sizeof(q));
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
while(hd<tl&&(Y(h(1))-Y(h(0)))<=K(i)*(X(h(1))-X(h(0))))
++hd;
ll j=h(0);
f[i]=Y(j)-K(i)*X(j)-B(i);
while(hd<tl&&(Y(t(0))-Y(t(1)))*(X(i)-X(t(0)))>=(Y(i)-Y(t(0)))*(X(t(0))-X(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}

[USACO08MAR]Land Acquisition G

思路

尝试直接推,但是发现推不出来。

首先可以发现一个性质:如果长方形 \(j\) 的长和宽都比长方形 \(i\) 的长和宽小,那么 \(j\) 可以直接并在 \(i\) 里面。

那么我们把这些土地以长为第一关键字,宽为第二关键字排序,把不会被完全覆盖的土地留下来,得到一些长单调递增,宽单调递减的土地。

如何实现这个操作?

排序之后长肯定是递增的。

如果一个宽比后面的小,那么它的长必然比后面的小,一定会被覆盖。

所以只要找出一个宽单调递减的序列就好了。

那么我们倒序遍历数组,记录当前的最大宽,如果当前宽比之前的最大宽大就加进去即可。

见代码GetLand部分。

然后就可以根据“长单调递增,宽单调递减”这个性质列出转移方程:

\[f_i=\min_{1\le j<i}\{f_{j}+w_{i}\times h_{j+1}\}
\]
\[f_{j}=-w_{i}\times h_{j}+f_i
\]

此时显然:

\[\begin{cases}
x=-h_j\\
y=f_j\\
k=w_i\\
b=f_i\\
\end{cases}
\]

然后就可以优化了。

代码

点击查看代码
namespace LAND{
inline bool cmp(Land a,Land b){
if(a.w==b.w)return a.h<b.h;
return a.w<b.w;
}
ll GetLand(){
sort(ld+1,ld+n+1,cmp);
ll len=0,max_h=0;
for_(i,n,1){
if(ld[i].h>max_h){
max_h=ld[i].h;
la[++len]=ld[i];
}
}
_for(i,1,len/2)swap(la[i],la[len-i+1]);
return len;
}
}
namespace DP{
ll q[N]={0},hd=1,tl=1;
inline ll X(ll i){return -la[i+1].h;}
inline ll Y(ll i){return f[i];}
inline ll K(ll i){return la[i].w;}
void Dp(){
memset(q,0,sizeof(q));
_for(i,1,n){
#define h(o) q[hd+o]
#define t(o) q[tl-o]
while(hd<tl&&(Y(h(1))-Y(h(0)))<=K(i)*(X(h(1))-X(h(0))))
++hd;
ll j=h(0);
f[i]=Y(j)-K(i)*X(j);
while(hd<tl&&(Y(t(0))-Y(t(1)))*(X(i)-X(t(0)))>(Y(i)-Y(t(0)))*(X(t(0))-X(t(1))))
--tl;
q[++tl]=i;
#undef h
#undef t
}
return;
}
}
\[\Huge{\mathfrak{The\ End}}
\]

「学习笔记」斜率优化dp的更多相关文章

  1. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  2. 学习笔记:斜率优化DP

    作为数学渣,先复习一下已知两点\((x_1, y_1)\), \((x_2, y_2)\),怎么求过两点的一次函数的斜率... 待定系数法代入 \(y = kx + b\) 有: \(x_1k + b ...

  3. hdu3507 斜率优化学习笔记(斜率优化+dp)

    QWQ菜的真实. 首先来看这个题. 很显然能得到一个朴素的\(dp\)柿子 \[dp[i]=max(dp[i],dp[j]+(sum[i]-sum[j])^2) \] 但是因为\(n\le 50000 ...

  4. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  5. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  6. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  7. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  8. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  9. 「学习笔记」单调队列优化dp

    目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...

随机推荐

  1. service继承baseService后无法注入dao的解决办法

    1.在set方法上加@Autowired 2.在set方法上加@Resource 这样子就可以拿到dao了

  2. Elasticsearch学习系列三(搜索案例实战)

    Query DSL Es提供了基于JSON的完整查询DSL(Domain Specific Language 特定域的语言)来定义查询.将查询DSL视为查询的AST(抽象语法树).它由两种子句组成: ...

  3. TCP和UDP协议的区别以及原理

    参考文章https://blog.csdn.net/weixin_38483133/article/details/123864253

  4. 数据访问 - EntityFramework集成

    前言 Masa提供了基于EntityFramework的数据集成,并提供了数据过滤与软删除的功能,下面我们将介绍如何使用它? MasaDbContext入门 安装.Net 6.0 新建ASP.NET ...

  5. IDEA的项目结构和IDEA的HelloWord

    IDEA首次驱动 1. 选择不导入任何设置,点击 OK 2. 选择 Create New Project 3. 点击 new 按钮,配置安装的 JDK9 版本 选择 JDK9 目录,点击确定 4. 不 ...

  6. DNS 系列(二):DNS 记录及工作方式,你了解吗?

    在上一篇<DNS 系列(一):为什么更新了 DNS 记录不生效?>中,我们主要讲解了 DNS 和 DNS 传播,知道了网络通信主要通过 IP 地址来进行,而域名系统(DNS)则是保证用户在 ...

  7. 2022-7-14 java_2 第七组 刘昀航

    @ 目录 一.java约定规范 1.关于建包 2.控制台输入(Scanner) 关于Scanner的bug 计算器小练习: 二. 1.数组 建立数组的三种方式: 数据类型的初始值: 2.二维数组 使用 ...

  8. 07 MySQL_SQL语言分类

    SQL语言分类 DDL Data Definition Language 数据定义语言 包括: create , alter ,drop , truncate; 不支持事务 DML Data Mani ...

  9. 2、MYSQL介绍

    一.mysql优点 1.成本低:开放源代码,一般可以免费试用,采用的是gpl协议 2.性能高:执行很快 3.简单:很容易安装和使用   二.DBMS分为两类: 1.基于共享文件系统的DBMS(Acce ...

  10. 了解有哪几个C标准&了解C编译管道

    下列哪个不是C标准.参考:C语言标准 小知识:C语言标准的发展 K&R C: 1978年,丹尼斯·里奇(Dennis Ritchie)和布莱恩·科尔尼干(Brian Kernighan)出版了 ...