算法

把一些 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. 修改jupyter notebook文件保存目录

    我们安装好jupyter notebook之后,打开的默认地址是在C盘,文件保存的文字也是C盘,会有其它乱七八糟的东西放一起,很不方便,所以可以换一个保存位置 1. 首先,在要存放文件的位置新建文件夹 ...

  2. c++(qt)播放wav文件的四种方式

    //方法一(要符合RIFF规范) 1 QSound::play("E:/Projects/报警声1-1.wav"); //方法二(要符合RIFF规范) 1 QSoundEffect ...

  3. FTP安装及使用

    通过网络传输数据的手段 1. ssh 2. http 3. nfs 4. rsync 5. ftp 6. samba ftp的简介: 1. ftp是应用层协议,是基于TCP 2. 使用21端口 FTP ...

  4. 一文看完vue3的变化之处

    在通读了vue的官网文档后,我记录下了如下这些相对于2.x的变化之处. 1.创建应用实例的变化 之前一般是这样: let app = new Vue({ // ...一些选项 template: '' ...

  5. 端口被占用的问题解决 Web server failed to start. Port ×× was already in use

    出现此问题是端口被占用了,只需要关闭正在使用的端口就行 解决思路: 1.在服务器中更改port端口号,改为不冲突,没有被占用的端口. 2.找出被占用的端口,结束被占用的端口 解决结束被占用的端口的方法 ...

  6. gitlab和jenkins做持续集成构建教程

    背景介绍 上一个轮回,我花了三篇文章的时间着重向大家介绍了在条件有限的情况下,如果优雅地进行前端发版和迭代.庆七一,热烈庆祝香港回归,人民生活水平越来越好,昨天上午我自掏腰包买了台服务器,决定由冷兵器 ...

  7. 《ASP.NET Core 6框架揭秘》样章[200页/5章]

    作为<ASP.NET Core 3 框架揭秘>的升级版,<ASP.NET Core 6框架揭秘>不仅针对ASP.NET Core 6的新特性进行了修订,并添加了若干原来没有的内 ...

  8. Unity3D学习笔记7——GPU实例化(2)

    目录 1. 概述 2. 详论 2.1. 实现 2.2. 解析 3. 参考 1. 概述 在上一篇文章<Unity3D学习笔记6--GPU实例化(1)>详细介绍了Unity3d中GPU实例化的 ...

  9. ShardingSphere 云上实践:开箱即用的 ShardingSphere-Proxy 集群

    本次 Apache ShardingSphere 5.1.2 版本更新为大家带来了三大全新功能,其中之一即为使用 ShardingSphere-Proxy chart 在云环境中快速部署一套 Shar ...

  10. SSH远程连接学校实验室服务器 一 python版本问题

    问题1: 连接上远程服务器后,输入 python 发现python版本只有2.7 .试图使用以下方式更新: sudo apt install python3.9 会出现如下报错信息: 解决方式: 一般 ...