学习笔记:斜率优化DP
作为数学渣,先复习一下已知两点\((x_1, y_1)\), \((x_2, y_2)\),怎么求过两点的一次函数的斜率...
待定系数法代入 \(y = kx + b\) 有:
\(x_1k + b = y_1\)
\(x_2k + b = y_2\)
两式相减有:
\(k = \frac{y_2 - y_1}{x_2 - x_1}\)
故事围绕着《算法竞赛进阶指南》的三一道例题展开:
引子
发现一个关键性质:
假如我们启动了一个任务\([l, r]\),那么它会对后面造成\(S * \sum_{i = r + 1}^{n} C_i\)的费用。
所以我们可以使用费用提前计算的方式优化算法:
设\(st\)为 \(t\) 的前缀和,设\(sum\)为\(C\)的前缀和
\(f[i]\) 表示安排完前 \(i\) 个任务的最小花费:
$f[i] = min(f[j] + (sum[i] - sum[j]) * t[i] + (sum[n] - sum[j]) * S) $
时间复杂度\(O(N ^ 2)\)
情况1. 斜率、横坐标皆单调递增
将上题推出的转移式子得\(min\)去掉观察:
\(f[i] = f[j] + (sum[i] - sum[j]) * t[i] + (sum[n] - sum[j]) * S\)
发现我们无法优化\(dp\)的原因是有与 \(i, j\) 两者都有关的乘积项,导致我们没有最优策略:
\(- sum[j] * t[i]\)
斜率优化
考虑把这个式子拆开转换为一次函数:\(y = kx + b\) 的形式。
- 将与 \(i,\ j\) 都有关系的乘积项作为 \(kx\),其中与 \(i\) 有关的作为 \(k\),与 \(j\) 有关的作为 \(x\)
- 将只与 \(j\) 有关系的值作为 \(y\)
- 其余的当做 \(b\)
则以上式子可以化成:
\(\underline{f[j]}_y = \underline{(t[i] + S)}_k * \underline{sum[j]}_x + \underline{f[i] - sum[i] * t[i] - sum[n] * S}_b\)
发现当 \(i\) 确定后,该一次函数的斜率 \(k\) 确定,则截距 \(b\) 越小, \(f[i]\) 越小。
我们将 \((x, y)\) 即 \((sum[j], f[j])\) 放在坐标系上。
则形象化可理解为一条直线从下往上平移,所碰到的第一个点即为最优解。
发现一个点如果被另外两个点围起来,永远不可能作为最优解。
删除了这些点后,发现相邻点之间的斜率为单调递增的,即构成一个凸包:
发现一个斜率 \(k\) 固定的直线所匹配的最优点满足:
- 其右边的斜率都 $ > k$
- 其左边的斜率都 \(< k\)
由于这道题斜率 \(t[i] + S\)、横坐标 \(sum[j]\) 皆单调递增。
- 由于横坐标递增,所以维护凸包时,每当加入一个点时:
若上面两个点构成的斜率大于这个点和上一个点的斜率,即不满足单调性,可以弹出队尾。即:
\(\frac{y_{q[tt]} - y_{q[tt - 1]}}{x_{q[tt]} - x_{q[tt - 1]}} >= \frac{y_{i} - y_{q[tt]}}{x_{i} - x_{q[tt]}}\)
由于斜率递增,所以 \(i + 1\) 的最优解一定在 \(i\) 的右边,所以一旦队头两个点构成的斜率 $ < $ 当前的斜率,可以弹出队头。即满足:
\(\frac{y_{q[hh + 1]} - y_{q[hh]}}{x_{q[hh + 1]} - x_{q[hh]}} < t[i] + S\)
然后队头的元素即为最优选择。
时间复杂度 \(O(N)\)
\(Tips:\)
- 由于除法会有精度问题,可以通过交叉相乘的形式比较大小
#include <cstring>
#include <cstdio>
#include <iostream>
#define x(a) (c[a])
#define y(a) (f[a])
#define k(a) (t[a] + S)
using namespace std;
typedef long long LL;
const int N = 300005;
int n, S;
LL t[N], c[N], q[N], f[N];
int main() {
scanf("%d%d", &n, &S);
for (int i = 1; i <= n; i++) scanf("%lld%lld", t + i, c + i);
for (int i = 1; i <= n; i++) t[i] += t[i - 1], c[i] += c[i - 1];
int hh = 0, tt = 0;
q[0] = 0;
for (int i = 1; i <= n; i++) {
while(hh < tt && (y(q[hh + 1]) - y(q[hh])) <= ((t[i] + S) * (x(q[hh + 1]) - x(q[hh])))) hh++;
f[i] = f[q[hh]] + (c[i] - c[q[hh]]) * t[i] + (c[n] - c[q[hh]]) * S;
while(hh < tt && ((y(q[tt]) - y(q[tt - 1])) * (x(i) - x(q[tt])) >= ((y(i) - y(q[tt])) * (x(q[tt]) - x(q[tt - 1]))))) tt--;
q[++tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}
情况2. 横坐标单调递增
此时的斜率不再递增了,也就是我们不能\(pop\_front\)了,不过我们仍可以维护凸包,然后保持单调性,二分。
时间复杂度\(O(Nlog_2N)\)
#include <cstring>
#include <cstdio>
#include <iostream>
#define x(a) (c[a])
#define y(a) (f[a])
#define k(a) (t[a] + S)
using namespace std;
typedef long long LL;
const int N = 300005;
int n, S, t[N], c[N], q[N];
LL f[N];
int main() {
scanf("%d%d", &n, &S);
for (int i = 1; i <= n; i++) scanf("%lld%lld", t + i, c + i);
for (int i = 1; i <= n; i++) t[i] += t[i - 1], c[i] += c[i - 1];
int hh = 0, tt = 0;
q[0] = 0;
for (int i = 1; i <= n; i++) {
int l = hh, r = tt;
while(l < r) {
int mid = (l + r) >> 1;
if((y(q[mid + 1]) - y(q[mid])) >= ((LL)k(i) * (x(q[mid + 1]) - x(q[mid])))) r = mid;
else l = mid + 1;
}
f[i] = f[q[r]] + (LL)(c[i] - c[q[r]]) * t[i] + (LL)(c[n] - c[q[r]]) * S;
while(hh < tt && ((y(q[tt]) - y(q[tt - 1])) * (x(i) - x(q[tt])) >= ((y(i) - y(q[tt])) * (x(q[tt]) - x(q[tt - 1]))))) tt--;
q[++tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}
例题
设 \(d[i]\) 为从 \(1\) 走到 \(i\) 的距离。
那么每条小猫最佳的出发时间应为 \(a[i] = t[i] - d[h[i]]\),如果要接上这只猫,必须大于这个时间出发。
我们将 \(a\) 数组排序,那么问题等价转换于把 \(m\) 个点划分成 \(p\) 个连续区间,使每一段到右端点的距离之和的总和最小。(内心 \(OS\):这不就是摆渡车的变种吗?)
那么朴素 \(dp\) 便很好列出了:
\(f[k][i]\) 表示将前 \(i\) 只小猫分成 \(k\) 组的最小总和。
设 \(sumA\) 为 \(a\) 数组的前缀和。
\(f[k][i] = min(f[k - 1][j] + a[i] * (i - j) - sumA[i] + sumA[j]) (0 <= j < i)\)
由于这里面有一个非常讨厌的 \(a[i] * -j\),所以我们考虑斜率优化:
\(\underline{f[k - 1][j] + sumA[j]}_y = \underline{a[i]}_k * \underline{j}_x + \underline{f[k][i] - a[i] * i + sumA[i]}_b\)
发现这里的横坐标、斜率都是单调递增,即情况 \(1\)。那么我们可以将不需要的直接踢出即可。
时间复杂度 \(O(PM)\)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100005, S = 105;
int n, m, P, d[N], a[N], q[N];
LL f[S][N], sum[N];
LL inline y(int i, int k) {
return f[k - 1][i] + sum[i];
}
int main() {
memset(f, 0x3f, sizeof f);
scanf("%d%d%d", &n, &m, &P);
for (int i = 0; i <= P; i++) f[i][0] = 0;
for (int i = 2; i <= n; i++)
scanf("%d", d + i), d[i] += d[i - 1];
for (int i = 1, h, t; i <= m; i++) {
scanf("%d%d", &h, &t); a[i] = t - d[h];
}
sort(a + 1, a + 1 + m);
for (int i = 1; i <= m; i++) sum[i] = sum[i - 1] + a[i];
for (int k = 1; k <= P; k++) {
int hh = 0, tt = 0;
for (int i = 1; i <= m; i++) {
while (hh < tt && (y(q[hh + 1], k) - y(q[hh], k)) <= (LL)a[i] * (q[hh + 1] - q[hh])) hh++;
f[k][i] = f[k - 1][q[hh]] + (LL)a[i] * (i - q[hh]) - (sum[i] - sum[q[hh]]);
while (hh < tt && (y(q[tt], k) - y(q[tt - 1], k)) * (i - q[tt]) >= (y(i, k) - y(q[tt], k)) * (q[tt] - q[tt - 1])) tt--;
q[++tt] = i;
}
}
printf("%lld\n", f[P][m]);
}
学习笔记:斜率优化DP的更多相关文章
- 学习笔记·斜率优化 [HNOI2008]玩具装箱
\(qwq\)今天\(rqy\)给窝萌这些蒟蒻讲了斜率优化--大概是他掉打窝萌掉打累了吧顺便偷了\(rqy\)讲课用的图 \(Step \ \ 1\) 一点小转化 事实上斜率优化是专门用来处理这样一类 ...
- 算法笔记--斜率优化dp
斜率优化是单调队列优化的推广 用单调队列维护递增的斜率 参考:https://www.cnblogs.com/ka200812/archive/2012/08/03/2621345.html 以例1举 ...
- 【学习笔记】动态规划—斜率优化DP(超详细)
[学习笔记]动态规划-斜率优化DP(超详细) [前言] 第一次写这么长的文章. 写完后感觉对斜优的理解又加深了一些. 斜优通常与决策单调性同时出现.可以说决策单调性是斜率优化的前提. 斜率优化 \(D ...
- 斜率优化DP学习笔记
先摆上学习的文章: orzzz:斜率优化dp学习 Accept:斜率优化DP 感谢dalao们的讲解,还是十分清晰的 斜率优化$DP$的本质是,通过转移的一些性质,避免枚举地得到最优转移 经典题:HD ...
- 【笔记篇】斜率优化dp(一) HNOI2008玩具装箱
斜率优化dp 本来想直接肝这玩意的结果还是被忽悠着做了两道数论 现在整天浑浑噩噩无心学习甚至都不是太想颓废是不是药丸的表现 各位要知道我就是故意要打删除线并不是因为排版错乱 反正就是一个del标签嘛并 ...
- APIO2010 特别行动队 & 斜率优化DP算法笔记
做完此题之后 自己应该算是真正理解了斜率优化DP 根据状态转移方程$f[i]=max(f[j]+ax^2+bx+c),x=sum[i]-sum[j]$ 可以变形为 $f[i]=max((a*sum[j ...
- 动态规划专题(五)——斜率优化DP
前言 斜率优化\(DP\)是难倒我很久的一个算法,我花了很长时间都难以理解.后来,经过无数次的研究加以对一些例题的理解,总算啃下了这根硬骨头. 基本式子 斜率优化\(DP\)的式子略有些复杂,大致可以 ...
- hdu3507Print Article(斜率优化dp)
Print Article Time Limit: 9000/3000 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others)To ...
- HDU-3507Print Article 斜率优化DP
学习:https://blog.csdn.net/bill_yang_2016/article/details/54667902 HDU-3507 题意:有若干个单词,每个单词有一个费用,连续的单词组 ...
- 一本通提高篇——斜率优化DP
斜率优化DP:DP的一种优化形式,主要用于优化如下形式的DP f[i]=f[j]+x[i]*x[j]+... 学习可以参考下面的博客: https://www.cnblogs.com/Xing-Lin ...
随机推荐
- 154. Find Minimum in Rotated Sorted Array II(循环数组查找)
Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...
- linux文件cat/tac/more/less/head/tail/find/vimdiff
ls查看目录文件里的文件: [root@localhost test]# ls a aa b c -d选项查看目录文件自身信息: [root@localhost test]# ll -d drw ...
- MYSQL学习(三) --索引详解
创建高性能索引 (一)索引简介 索引的定义 索引,在数据结构的查找那部分知识中有专门的定义.就是把关键字和它对应的记录关联起来的过程.索引由若干个索引项组成.每个索引项至少包含两部分内容.关键字和关键 ...
- 计算机&编程语言发展史
计算机&编程语言发展史 编辑于2020-11-18 计算机的基本组成 计算机的发展经历了哪几代? 第一代 电子管计算机 第二代 晶体管计算机 第三代 集成电路计算机 第四代 大规模和超大规模集 ...
- 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务
权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务. 权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量 ...
- 基于gin的golang web开发:永远不要相信用户的输入
作为后端开发者我们要记住一句话:"永远不要相信用户的输入",这里所说的用户可能是人,也可能是另一个应用程序."永远不要相信用户的输入"是安全编码的准则,也就是说 ...
- 这份SpringMVC执行原理笔记,建议做java开发的好好看看,总结的很详细!
什么是SpringMVC? Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供的web模块,包含了开发Web 应用程 ...
- Java基础教程——缓冲流
缓冲流 "缓冲流"也叫"包装流",是对基本输入输出流的增强: 字节缓冲流: BufferedInputStream , BufferedOutputStream ...
- IEEE754标准浮点数表示与舍入
原文地址:https://blog.fanscore.cn/p/26/ 友情提示:本文排版不太好,但内容简单,请耐心观看,总会搞懂的. 1. 定点数 对于一个无符号二进制小数,例如101.111,如果 ...
- devc++编译时 undefined reference to `__imp_WSAStartup'
socket编程时遇到的问题: