斜率优化DP入门
斜率优化DP入门
参考蓝书。
斜率优化的模型一般是:
其中, 仅和 或 有关时,我们可以想到单调队列优化
当其同时与 有关,我们可以想到斜率优化
「TYVJ1098」任务安排 1
题面
个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 个任务被分成若干批,每批包含相邻的若干任务。从时刻 开始,这些任务被分批加工,第 个任务单独完成所需的时间是 。在每批任务开始前,机器需要启动时间 ,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数 。请确定一个分组方案,使得总费用最小。 例如:如果分组方案是 ,则完成时间分别为,费用 ,总费用就是 。
输入
第一行是 。 第二行是 。 下面 行每行有一对数,分别为 和 ,均为不大于 的正整数,表示第 个任务单独完成所需的时间是 及其费用系数 。
输出
一个数,最小的总费用。
样例
样例输入1
5
1
1 3
3 2
4 3
2 3
1 4
样例输出1
153
解题
考虑 DP
我们首先设状态: 表示前 个任务分成 批执行的最先费用
那么有状态转移方程:
其中,
其中,
即,考虑第 批任务执行的是 个任务
但这样是 ,爆了,我们需优化。
注意到 题目并没有规定分成多少批次
之所以需要批次,是因为想知道有多少次启动时间S,从而计算出每批任务完成的时间
实际上,可以将每批任务花费的启动时间S,对之后任务的影响提前计算
状态:,表示前 个任务划分成若干批执行的最小费用。
考虑当前批次执行的任务,有状态转移方程:
怎么理解呢?
当前批次执行的任务为第 个任务
第一部分是直接把 当做这批的结束时间(之前的启动时间已经在 中)
第二部分,机器的启动时间会对第 个任务以后的所有任务产生影响,提前将影响累加到最小费用中
时间复杂度
这其实是 **费用提前计算 **的经典思想
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e3+5;
int n,s,t[N],c[N],f[N];
signed main(){
scanf("%lld%lld",&n,&s);
for(int i=1;i<=n;++i)
scanf("%lld%lld",&t[i],&c[i]),
t[i]+=t[i-1],c[i]+=c[i-1];
memset(f,127,sizeof(f));
f[0]=0;
for(int i=1;i<=n;++i)
for(int j=0;j<i;++j)
f[i]=min(f[i],f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j]));
printf("%lld\n",f[n]);
return 0;
}
「POJ1180」任务安排 2
题面
同任务安排 1
数据范围:
解题
芜湖, 过不了了
我们考虑对状态转移方程进行变形
可以把去掉 想成,直接把 当做 的决策点
上面最后得到的是以 为主元的式子,这启发我们:
将每个决策点 ,视为二维平面上的点。
将 看作是横坐标, 看作是纵坐标,那么上式就是一个形如 的一条直线
我们的目的是让 有尽量小的取值,
也就是让 有尽量小的取值,因为 中,除了 都是定值
也就是挑选一个合适的 ,使得 尽量小
也就是将直线 从坐标轴的最下方往上移,碰到的第一个点就是最优决策点,因为此时的平移距离 最小。
这就是“斜率”的含义了,那么“优化”呢?
回顾我们DP优化的关键:及时排除无用决策
可以想到,一些决策点 是无用的。
假设存在三个决策 ,,,对应的决策点为 ,设三点分别为 。
设 ,
由图可知,在上凸情况下, 点是无用的,下凸情况下,如果:
才是有用的。
我们按照上述的规则,排除掉所有无用决策点,将剩下的点集相邻两点连线
形成的线段的斜率从左到右是单调递增,实际上需要维护的是一个下凸壳
我们可以使用单调队列
哪一个点是最优决策呢?
对于斜率为 的直线,若某个点左侧线段的斜率小于 ,右侧线段的斜率大于 ,那么该点就是最优决策点
如何在斜率单调递增的队列中找到最优决策点?
二分,那么时间复杂度为 ,已经足够过掉这道题了。
但,还能不能再优化?
我们观察刚刚的式子:
-
是单调递增的,新的决策点的横坐标一定大于之前所有决策点的横坐标
-
斜率 单调递增的
由上述两点,我们只需要维护相邻两点线段斜率大于 的决策点,那么最优决策点就是队头
具体的,对于每个状态 :
- 检查队头的两个决策 和 ,若斜率 则将 出队,继续检查队头
- 直接取出队头 为最优决策,计算 。
- 将新决策 加入队尾,插入前,若三个决策点 不满足下凸,则 是无用决策,将 出队,继续检查队尾。
这就是优化了
• 维护队列中相邻两个元素的某种“比值”的“单调性”
• 因为该比值对应坐标系中的斜率
• 所以称为斜率优化
• 英文称为 (直译:凸壳优化策略)
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 30005;
int read(){
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=10*x+ch-'0',ch=getchar();
return x*f;
}
int n,s;
int q[N],t[N],c[N];
ll st[N],sc[N],f[N];
double calc(int i,int j){
return double(f[j]-f[i])/(sc[j]-sc[i]);
}
int main() {
memset(f,0x3f,sizeof(f));
n=read();s=read();
for(int i=1;i<=n;++i){
t[i]=read();c[i]=read();
st[i]=st[i-1]+t[i];
sc[i]=sc[i-1]+c[i];
}
int l=1,r=0;
q[++r]=f[0]=0;
for(int i=1;i<=n;++i){
while(l<r&&calc(q[l],q[l+1])<=s+st[i])l++;
f[i]=f[q[l]]-(s+st[i])*sc[q[l]]+st[i]*sc[i]+s*sc[n];
while(l<r&&calc(q[r-1],q[r])>calc(q[r],i))r--;
q[++r]=i;
}
printf("%d",f[n]);
return 0;
}
「BZOJ2726」任务安排 3
题面
同任务安排 1
数据范围:
解题
芜湖, 可以为负数,那么斜率 不在递增了
不能仅仅只维护相邻两点线段斜率大于 的决策点,需要维护所有下凸壳的决策点
如何找到最优决策?单调队列中二分
时间复杂度为
代码
#include <cstdio>
typedef long long ll; int n,s,q[300001];
ll f[300001],st[300001],sc[300001];
ll in(){
ll ans=0; int f=1; char c=getchar();
while ((c<48||c>57)&&c!='-') c=getchar();
if (c=='-') c=getchar(),f=-f;
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans*f;
}
int bs(int i,int k,int l,int r){
if (l==r) return q[l];
while (l<r){
int mid=(l+r)>>1;
if (f[q[mid+1]]-f[q[mid]]<=k*(sc[q[mid+1]]-sc[q[mid]])) l=mid+1;
else r=mid;
}
return q[l];
}
int main(){
n=in(); s=in(); q[1]=0;
for (int i=1;i<=n;i++)
st[i]=st[i-1]+in(),sc[i]=sc[i-1]+in();//费用提前计算
f[0]=0; int l=1,r=1;
for (int i=1;i<=n;i++){
int ans=bs(i,s+st[i],l,r);//二分求答案
f[i]=f[ans]-(s+st[i])*sc[ans]+st[i]*sc[i]+s*sc[n];//动态规划
while (l<r&&(f[q[r]]-f[q[r-1]])*(sc[i]-sc[q[r]])>=(f[i]-f[q[r]])*(sc[q[r]]-sc[q[r-1]])) r--;//队尾不满足单调递增
q[++r]=i;
}
return !printf("%lld",f[n]);
}
————————————————
版权声明:代码为CSDN博主「ssl_xjq_逐风之刃」的原创,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sugar_free_mint/article/details/81949236
注意,这里如果我们直接用之前的 函数算斜率的话会被卡精度,需要用乘法。
任务安排4
为正数, 可以为负数。
回顾那个式子:
方法一
新增加的决策点的横坐标 不再单调递增,会插入到凸壳中间的位置,队列不能实现插入操作
什么东西支持插入、维护递增?
平衡树!
我们可以利用平衡树维护斜率单调性
方法二:
可以倒序DP,设计一个状态转移方程,让 为横坐标, 为斜率的一项,转为为 任务安排3 的情况,使用单调队列维护凸壳,使用二分查找求出最优策略
任务安排5
均可以为负数。
康康这位神犇的啦 『任务安排 斜率优化及其变形』 - Parsnip - 博客园
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】