算法

把一些 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. TDSQL|三篇论文入选国际顶会SIGMOD,厉害了腾讯云数据库

    好消息!6月13日,腾讯云数据库三篇论文再次入选数据库行业顶会SIGMOD,被SIGMOD 2022 Research Full Paper(研究类长文)收录. 本次被收录的研究成果中,新型数据结构设 ...

  2. ExtJS 布局-VBox布局(VBox layout)

    更新记录: 2022年6月11日 优化文章结构. 2022年6月9日 发布. 2022年6月1日 开始. 1.说明 vbox布局类似auto布局,将子组件一个接一个垂直向下放置,既可以在水平方向也可以 ...

  3. 4.使用CFileDialog打开文件对话框,获得文件路径 -windows编程

    引言:没想到2022年还有很多工业软件公司依然使用MFC,微软也一直在更新MFC的库,这次使用MFC封装的CFileDialog类,写一个获得选定文件路径,名称,扩展名的程序. 个人技术博客(文章整理 ...

  4. Java获取汉字的大小写拼音码(汉字的拼音首字母)

    import java.io.UnsupportedEncodingException; /** * 获取拼音码 * * @author xmj * */ public class GetPinyin ...

  5. Codeforces Round #802 (Div. 2)

    题集链接 A Optimal Path 水 代码 #include <bits/stdc++.h> #define endl "\n" using namespace ...

  6. ArrayList源码深度剖析,从最基本的扩容原理,到魔幻的迭代器和fast-fail机制,你想要的这都有!!!

    ArrayList源码深度剖析 本篇文章主要跟大家分析一下ArrayList的源代码.阅读本文你首先得对ArrayList有一些基本的了解,至少使用过它.如果你对ArrayList的一些基本使用还不太 ...

  7. APISpace 空号检测API接口 免费好用

    空号检测也称空号在线过滤,在线筛号,号码在线清洗.空号检测平台借助第五代大数据空号检测系统,为用户提供高精准的空号检测.号码过滤.号码筛选.号码清洗等众多号码检测功能,让用户快速准确的检测出活跃号.空 ...

  8. PHP操作路由器

    用PHP操作路由器 我们经常会碰到需要自动换IP的需求,比方模拟点击投票,数据采集被封IP,Alexa作弊等等,也就是需要经常换IP的,我们都可以通过PHP控制路由器来换IP,这样就不需要用按键精灵搞 ...

  9. PHP进阶玩法

    1. 删除不必要的模块. PHP随带内置的PHP模块.它们对许多任务来说很有用,但是不是每个项目都需要它们.只要输入下面这个命令,就可以查看可用的PHP模块: # php - m  一旦你查看了列表, ...

  10. YII缓存操作

    //文件依赖 $dependency = new \yii\caching\FileDependency(['filename'=>'hw.txt'])}; $cache->add(&qu ...