这个真的好容易啊 ——wzw

斜率优化dP

例题

[SDOI2012] 任务安排

毒瘤题,让我惨淡经营了两天。这道题luogu有简单版,可以先去看简单版。

显然这是一只DP题,直接开始推狮子。令 dp[i] 表示以第 \(i\) 个任务为终止时的最小花费。\(t\) 和 \(w\) 都表示的是前缀和,那么有 \(dp[i]=min\{dp[j]+(t[i]+S*k)(w[i]-w[j])\}\) 。观察狮子,\(k\) 是完全不知道的,也就是转移所依赖的 dp[j] 是否有分组是完全不知道的,所以无法转移。不妨继续思考,假如在第 \(1\sim i\) 分了一组,那么 \(i+1\sim n\) 中必然也会分组,也就是 \(s+1\sim n\) 的时间必然会增加一个 \(S\)。于是有:

\[\begin{split}
dp[i]&=min\{dp[j]+t[i]*(w[i]-w[j])+S*(s[n]-s[i])\} \\
&=min\{dp[j]-t[i]*w[j]\}+t[i]*w[i]+S*(s[n]-s[i])\\
&j\in [0, i-1]
\end{split}
\]

观察min中的狮子,把它们单独拎出来:\(dp[j]=dp[i]+t[i]*w[j]\)。其中 \(dp[i]\) 为截距,\(t[i]\) 为斜率,只需要用单调队列维护一个下凸包即可。但考虑到 \(Ti\) 存在负数,那么斜率就不再具有单调性,用二分维护即可。复杂度 \(O(nlogn)\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 10;
int n, s, t[N], c[N], p[N], dp[N], tail;
inline int up(int a, int b){ return dp[b] - dp[a]; }
inline int dn(int a, int b){ return c[b] - c[a]; }
inline int get(int l, int r, int slp){
while(l < r){
int mid = (l + r) >> 1;
if(up(p[mid], p[mid+1]) <= slp*dn(p[mid], p[mid+1])) l = mid + 1;
else r = mid;
} return l;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>s;
for(int i=1, j; i<=n; ++i){
cin>>t[i]>>c[i];
t[i] += t[i-1], c[i] += c[i-1];
}
for(int i=0, j; i<=n; ++i){
j = get(1, tail, t[i]);
j = p[j];
dp[i] = dp[j] + t[i]*(c[i]-c[j]) + s*(c[n]-c[i]);
while(tail > 1 && up(p[tail-1], p[tail])*dn(p[tail], i) >= up(p[tail], i)*dn(p[tail-1], p[tail])) --tail;
p[++tail] = i;
} return cout<<dp[n], 0;
}

注意几点:

  • 维护时应严格递增,不要留有斜率相等的点。
  • 维护时应看准 \(tail\) 的范围,如果涉及到 \(tail-1\),那么 \(tail>1\)
  • 因为涉及double,尽量不要直接把斜率算出来,而是使用up和down。

[APIO2010] 特别行动队

当你打完任务安排后就会发现这道题简单的跟1+1似的。

一只小地痞。令 dp[i] 表示以第 \(i\) 名士兵为界限分组时的最大战斗力值。\(w\) 表示前缀和,令 \(W=w[i]-w[j]\)。于是有:

\[dp[i]=max\{dp[j]+aW^2+bW+c\}
\]

展开后得:

\[dp[i]=max\{dp[j]-2*a*w[i]*w[j]+a*w[j]^2-b*w[j]\}+a*w[i]^2+b*w[i]+c
\]

观察到只有max里面的狮子和 \(j\) 有关,于是提出来并变形得:

\[dp[j]+a*w[j]^2-b*w[j]=dp[i]+2*a*w[i]*w[j]
\]

显然 \(2a*w[i]\) 为斜率。因为需要求max,所以维护一个上凸包(斜率严格递减)。接着就是斜率地痞板子了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 1;
int n, a, b, c, w[N], dp[N], p[N], tail, head = 1;
inline int up(int x, int y){ return dp[y] + a*w[y]*w[y] - b*w[y] - dp[x] - a*w[x]*w[x] + b*w[x]; }
inline int dn(int x, int y){ return w[y] - w[x]; }
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n>>a>>b>>c;
for(int i=1; i<=n; ++i) cin>>w[i], w[i] += w[i-1];
for(int i=0; i<=n; ++i){
while(head < tail && up(p[head], p[head+1]) >= 2*a*w[i]*dn(p[head], p[head+1])) ++head;
dp[i] = dp[p[head]] + a*(w[i]-w[p[head]])*(w[i]-w[p[head]]) + b*(w[i]-w[p[head]]) + c;
while(tail > head && up(p[tail-1], p[tail])*dn(p[tail], i) <= up(p[tail], i)*dn(p[tail-1], p[tail])) --tail;
p[++tail] = i;
dp[0] = 0;
// 因为可以从dp[0]转移,所以要把0添加到队列里去
// 但是这样计算的话dp[0]=c,所以要把dp[0]设为0
} return cout<<dp[n], 0;
}

[ZJOI2007] 仓库建设

当你打完任务安排后就会发现这道题简单的跟1+1似的。

一道简单坑人的小地痞。令 dp[i] 表示在第 \(i\) 个工厂建仓库的最大价格。那么在这个工厂和上一个工厂之间的所有工厂的存货都要运送到仓库 \(i\) 里。那么对于一个在此之间工厂 \(k\),它的送货价值就为 \(p[k]*(x[i]-x[k])=p[k]*x[i]-p[k]*x[k]\),所以我们可以预处理出所有 \(p[k]\) 的前缀和 \(P\) 和所有 \(p[k]*x[k]\) 的前缀和 \(T\)。于是地痞方程为:

\[dp[i]=min\{dp[j]+T[i-1]-T[j]-P[j]*x[i]+c[i]\}
\]

调整得:

\[dp[i]=min\{dp[j]-T[j]-P[j]*x[i]\}+c[i]+T[i-1]
\]

发现min里的狮子都和 \(j\) 有关,于是提出来:\(dp[j]-T[j]=dp[i]+x[i]*P[j]\)。显然 \(x[i]\) 为斜率。因为求min,所以维护下凸包。

hack 数据中末尾会有一连串 \(p=0\) 的工厂,不会对答案产生任何贡献(他们无需运送货物,也不会被作为仓库),但是 dp 过程中会把他们算进去,因此答案应当取最后一个有货物的工厂作为仓库时的最小花费。—— Ithea686

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
int n, x[N], c[N], t[N], w[N], dp[N], p[N], tail, head = 1, ans = LONG_LONG_MAX;
inline int up(int a, int b){ return dp[b] + t[b] - dp[a] - t[a]; }
inline int dn(int a, int b){ return w[b] - w[a]; }
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin>>n;
for(int i=2; i<=n+1; ++i){
cin>>x[i]>>w[i]>>c[i];
t[i] = t[i-1] + x[i]*w[i];
w[i] += w[i-1];
}
for(int i=1; i<=n+1; ++i){
while(head < tail && up(p[head], p[head+1]) <= x[i]*dn(p[head], p[head+1])) ++head;
dp[i] = dp[p[head]] + x[i]*(w[i-1]-w[p[head]]) - t[i-1] + t[p[head]] + c[i];
while(head < tail && up(p[tail-1], p[tail])*dn(p[tail], i) >= up(p[tail], i)*dn(p[tail-1], p[tail])) --tail;
p[++tail] = i;
}
for(int i=n+1; i>=1; --i){
ans = min(ans, dp[i]);
if(w[i]-w[i-1]) break;
}
return cout<<ans, 0;
}

[学习笔记] 斜率优化DP - DP的更多相关文章

  1. 学习笔记·斜率优化 [HNOI2008]玩具装箱

    \(qwq\)今天\(rqy\)给窝萌这些蒟蒻讲了斜率优化--大概是他掉打窝萌掉打累了吧顺便偷了\(rqy\)讲课用的图 \(Step \ \ 1\) 一点小转化 事实上斜率优化是专门用来处理这样一类 ...

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

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

  3. 【学习笔记】动态规划—各种 DP 优化

    [学习笔记]动态规划-各种 DP 优化 [大前言] 个人认为贪心,\(dp\) 是最难的,每次遇到题完全不知道该怎么办,看了题解后又瞬间恍然大悟(TAT).这篇文章也是花了我差不多一个月时间才全部完成 ...

  4. 队列优化和斜率优化的dp

    可以用队列优化或斜率优化的dp这一类的问题为 1D/1D一类问题 即状态数是O(n),决策数也是O(n) 单调队列优化 我们来看这样一个问题:一个含有n项的数列(n<=2000000),求出每一 ...

  5. 汇编入门学习笔记 (七)—— dp,div,dup

    疯狂的暑假学习之  汇编入门学习笔记 (七)--  dp.div.dup 參考: <汇编语言> 王爽 第8章 1. bx.si.di.和 bp 8086CPU仅仅有4个寄存器能够用 &qu ...

  6. 深度学习笔记:优化方法总结(BGD,SGD,Momentum,AdaGrad,RMSProp,Adam)

    深度学习笔记:优化方法总结(BGD,SGD,Momentum,AdaGrad,RMSProp,Adam) 深度学习笔记(一):logistic分类 深度学习笔记(二):简单神经网络,后向传播算法及实现 ...

  7. 算法笔记--斜率优化dp

    斜率优化是单调队列优化的推广 用单调队列维护递增的斜率 参考:https://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html 以例1举 ...

  8. 一类斜率优化的dp(特有性质:只能连续,不能交叉)

    hdu3480 给定一个有n个数的集合,将这个集合分成m个子集,要求子集的并等于全集求花费最小. 花费为该子集的(最大数-最小数)的平方. 我们将n个数排序, a < b < c < ...

  9. [NOI2014]购票 --- 斜率优化 + 树形DP + 数据结构

    [NOI2014]购票 题目描述 今年夏天,NOI在SZ市迎来了她30周岁的生日. 来自全国 n 个城市的OIer们都会从各地出发,到SZ市参加这次盛会. 全国的城市构成了一棵以SZ市为根的有根树,每 ...

  10. Luogu P5468 [NOI2019]回家路线 (斜率优化、DP)

    题目链接: (luogu) https://www.luogu.org/problemnew/show/P5468 题解: 爆long long毁一生 我太菜了,这题这么简单考场上居然没想到正解-- ...

随机推荐

  1. C#/.NET/.NET Core拾遗补漏合集(24年6月更新)

    前言 在这个快速发展的技术世界中,时常会有一些重要的知识点.信息或细节被忽略或遗漏.<C#/.NET/.NET Core拾遗补漏>专栏我们将探讨一些可能被忽略或遗漏的重要知识点.信息或细节 ...

  2. 关于error: failed to push some refs to如何解决

    Smiling & Weeping ---- 在你的门前,我堆起一个雪人,代表笨拙的我,把你久等 常见的错误报错内容基本都是error: failed to push some refsto' ...

  3. 【ASeeker】Android 源码捞针,服务接口扫描神器

    ASeeker是一个Android源码应用系统服务接口扫描工具. 项目已开源: ☞ Github ☜ 如果您也喜欢 ASeeker,别忘了给我们点个星. 说明 ASeeker 项目是我们在做虚拟化分身 ...

  4. mysql这个垃圾迁移数据费劲半天

    mysql垃圾得很!  对于一些小系统还可以.大型系统自己找麻烦--必须有高昂的人工费! 没有高昂的人工(必须有好的程序员和工程师才能解决一些复杂的问题),构建基于大量数据的应用,非常麻烦. 而这些费 ...

  5. vulnhub - BREACH: 1

    vulnhub - BREACH: 1 描述 作为多部分系列中的第一部分,Breach 1.0 旨在成为初学者到中级的 boot2root/CTF 挑战.解决将需要可靠的信息收集和持久性相结合.不遗余 ...

  6. Mybatis ResultMap复杂对象一对多查询结果映射之collection

    Mybatis复杂对象一对多映射配置ResultMap的collection collection:一对多查询结果映射,比如user有多个订单 表结构 项目结构图 pom.xml <?xml v ...

  7. SpringBoot异步任务EnableAsync

    什么是一部任务和使用场景:适用于处理log.发送邮件.短信...等 下单接口->查库存 1000 余额校验 1500 风控用户 1000 启动类里面使用@EnableAsync注解开启功能,自动 ...

  8. js 异步 任务 题目解析(chatgpt bug了?)

    最近遇到一道题如下,求输出结果 感觉还是蛮有意思的,找chatgpt做了一下 我是题 async function async1(){ console.log('1'); await async2() ...

  9. Vscode 一次选中多行 光标一次定位多行

    1 . 鼠标点击开始位置(定位到行首时,鼠标就点击第一行的行首:定位到行尾时,鼠标就点击第一行的行尾:) 2.  按住shift+alt 点击结束的位置(定位到行首时,鼠标就点击最后一行的行首:定位到 ...

  10. [oeasy]python0015_十六进制_hexadecimal_字节形态_hex函数

    ​ 十六进制(hexadecimal) 回忆上次内容 上次数制可以转化 bin(n)可以把数字转化为 ​​2进制​ binary 接收一个整数(int) 得到一个二进制数形式的字符串 ​ 编辑 数字在 ...