算法

把一些 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. dubbo的消费者是怎么获取提供者服务接口引用的?

    本文主要解读dubbo消费者是如何引用服务端接口的,是如何像本地调用一样调用远程服务的. 并试着从设计者的角度思考,为何这样设计. @Component public class DubboConsu ...

  2. MVVM,MVC,MVP的区别

    MVC.MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化开发效率. 在开发单页面应用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文 ...

  3. Vue回炉重造之图片加载性能优化

    前言 图片加载优化对于一个网站性能好坏起着至关重要的作用.所以我们使用Vue来操作一波.备注 以下的优化一.优化二栏目都是我自己封装在Vue的工具函数里,所以请认真看完,要不然直接复制的话,容易出错的 ...

  4. python+anaconda+pycharm的使用

    研一开学的时候开始接触了这些,但是对于其各种功能感到十分混乱,现在通过这篇博文将其功能详细的写出来. 1.python解释器 首先要了解python解释器,我们俗称的下载python也就是下载pyth ...

  5. RPA人力资源简历筛选机器人

    简历自动筛选及分析机器人,支持前程无忧.猎聘 1.自动登录招聘网站 2.自动填充简历筛选条件 3.RPA依次读取所筛选的简历信息 4.自动将简历数据复制到本地文档中 5.完成简历信息收集及分析表 6. ...

  6. 深入解析kubernetes中的选举机制

    Overview 在 Kubernetes的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumt ...

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

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

  8. 测试右移:线上质量监控 ELK 实战

    目录 [测试右移]介绍 ELK Stack 介绍 ELK 监控体系搭建 ES & Kibana 搭建 Nginx 日志自动采集 Nginx Agent 安装 Nginx 服务器 数据分析 Lo ...

  9. # Vue3 toRef 和 toRefs 函数

    Vue3 toRef 和 toRefs 函数 上一篇博文介绍了 vue3 里面的 ref 函数和 reactive 函数,实现响应式数据,今天主要来说一下 toRef 函数和 toRefs 函数的基本 ...

  10. Collection集合和Collection集合常用功能

    Collection集合常用功能 方法: boolean add(E e); 向集合中添加元素 boolean remove(E e); 删除集合中的某个元素 void clear(); 清空集合所有 ...