【前言】

在补Codeforce的DP时遇到一个比较新颖的题,然后在知乎上刚好 hycc 桑也写了这道题的相关题解,这里是作为学习并引用博客的部分内容

这道题追根溯源发现2016年这个算法已经在APIO2016烟花表演与Codeforces 713C引入,自那之后似乎便销声匿迹了。相关题型数量也较少,因而在这里结合前辈们的工作做一些总结。---by hycc

问题引入:Codeforces 713C

题目链接:Here

题意:

  • 给定 \(n\) 个正整数 \(a_i\) ,每次操作可以选择任意一个数将其 \(+1\) 或 \(-1\) ,问至少需要多少次操作可以使得 \(n\) 个数保持严格单增

  • 数据范围:\(1\le n\le 3000,1\le a_i\le 10^9\)

对我来说这道题其实和曾经写过的 POJ-3666:求不升的DP是一样的

这个题是求升序的DP,那么有什么变化呢

不升的条件是:\(a_i -a_j \ge 0\)

升序的条件是:\(a_i -a_j \ge i - j\) 对任意 \(i,j\) 均满足

有没有理解到什么?移项有:\(a_i - i \ge a_j - j\)

所以将 \(a\)​ 数字变形一下就和POJ3666就是一个题!

【AC Code】

const int N = 3100;
int n, m;
ll f[N][N], a[N], b[N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] = a[i] - i;
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
m = 1;
for (int i = 2; i <= n; ++i) if (b[i] != b[i - 1]) b[++m] = b[i];
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; ++i) {
ll Min = LLONG_MAX;
for (int j = 1; j <= m; j++) {
Min = min(Min, f[i - 1][j]);
f[i][j] = abs(b[j] - a[i]) + Min;
}
}
ll ans = LLONG_MAX;
for (int i = 1; i <= m; ++i) ans = min(ans, f[n][i]);
cout << ans << "\n";
}

当然上面说的思路并不是本篇博客实际想表达,以下才是正文

对于朴素的 \(\mathcal{O}(n^2)\ DP\)​ :

一个显然的性质:如果不是“严格单增”而是“严格非降”,那么最终形成的严格非降序列,其中每个元素一定属于 \(\{a_i\}\)​

将元素离散化后可以设计 \(f_{i,j}\) 表示到第 \(i\) 个数取 \(j\) 的最少操作数

那么有转移 \(f_{i,j} = \min\limits_{k\le j}f_{i-1,k} + | a_i - j|\)​ ,记录 \(f_{i-1,*}\)​ 的前缀 \(\min\)​ 即可做到 \(\mathcal{O}(n^2)\)​

至于如何做到“严格非降”,\(a_{i-1} < a_i,a_{i -1} \le a_i - i,a_{i-1}-(i-1)\le a_i - i\)

于是令 \(a_i = a_i - i\) 即可。

赛后的评论区中出现了一种 \(\mathcal{O}(Nlog\ N)\)的做法,也就是 Slope Trick算法的第一次现身(?)


Slope Trick:解决一类凸代价函数的DP优化问题

当序列DP的转移代价函数为

连续

分段线性函数

凸函数

时,可以通过记录分段函数的最右一段 \(f_r(x)\) 以及其分段点 \(L\)​ 实现快速维护代价的效果。

如:\(f(x)=\left\{\begin{array}{rr}
-x-3 & (x \leq-1) \\
x & (-1<x \leq 1) \\
2 x-1 & (x>1)
\end{array}\right.\)

可以仅记录 \(f_r(x) = 2x - 3\) 与分段点 \(L_f = \{-1,-1,1\}\) 来实现对该分段函数的存储。

注意:要求相邻分段点之间函数的斜率差为 \(1\) ,也就是说相邻两段之间斜率差 \(\ge 1\) 的话,这个分段点要在序列里出现多次。

优秀的性质:

\(F(x),G(x)\) 均为满足上述条件的分段线性函数,那么 \(H(x) =F(x)+G(x)\) 同样为满足条件的分段线性函数,且 \(H_r(x) = F_r(x) + G_r(x),L_H = L_F \bigcup L_G\) 。

该性质使得我们可以很方便得运用数据结构维护 \(L\)​ 序列。

回顾:Codeforces 713C

转移方程为 \(f_{i,j} = \min\limits_{k\le j}f_{i-1,k} + |a_i - j|\)​​

令 \(F_{i}(x)=f_{i, x}, G_{i}(x)=\min\limits _{k \leq x} f_{i-1, k}=\min \limits_{k \leq x} F_{i-1}(k)\)

那么有 \(F_i(x) = G_i(x) + |x -a_i|\) ,其中 \(F_i,G_i\) 均为分段线性函数。

\(G_i\) 求的是 \(F_{i-1}\) 的关于函数值的前缀最小值,由于 \(F_{i-1}\) 是一个凸函数,因而其最小值应该在斜率 \(=0\) 处取得,其后部分可以舍去。

而每次由 \(G_i(x)\) 加上 \(|x-a_i|\) ,等价于在 \(L\) 中添加两个分段点 \(\{a_i,a_j\}\)

因而 \(G_i\) 各段的函数斜率形如 \(\{...,-3,-2,-1,0\}\) ,加上 $|x-a_i| $后斜率变为 \(\{...,-3,-2,-1,0,1\}\) ,因而需要删除末尾的分段点。

具体实现中:使用大根堆维护分段点单调有序,每次加入两个 \(a_i\) ,再弹出堆顶元素。

总复杂度 :\(\mathcal{O}(n\ log\ n)\)


\[QAQ
\]

Codeforces 1534G

题意:

一个无限大的二维平面上存在 \(n\)​ 个点 \((x_i,y_i)\)​ 均需要被访问一次,从 \((0,0)\) 出发,每次可以向右或向上移动一个单位。

可以在任意位置 \((X,Y)\) 访问 \((x_i,y_i)\) 并付出 \(\max\{|X-x_i|,|Y-y_i|\}\) 的代价(访问后依然留在 \((X,Y)\) )。同一位置可以访问多个点。

问:至少需要花费多少代价才能使得所有点均被访问?

数据范围: \(1\le n\le 800000,0\le x_i,y_i\le 10^9\)

结合上图可以看出,对于点 \((X,Y)\) ,一定会选择路径与直线 \(x+y=X+Y\)(红线)的交点 \((x,y)\) 处作为访问的发起点(在这条线上 \(|X-x| = |Y-y|\) )。

考虑到这条红线是倾斜的,因而将坐标系顺时针翻转 \(45^°\)​,即 \((x+y,x-y)\) 代替 \((x,y)\)

此时,每次移动变为 \((x+1,y-1)\)​ 或 \((x+1,y+1)\)

把所有点按新的 \(x\) 坐标排序,即可转为序列上的问题。

设值域为 \(M\) ,则很容易写出 \(\mathcal{O}(nM)\) 的转移方程:

\(f_{i,Y}\) 表示从左到右考虑到横坐标为 \(x_i\) 的所有点,当前路径到了 \((x_i,Y)\) 的最小代价,

那么有

$f_{i,Y}=\min\limits_{Y-\left|x_{i}-x_{i-1}\right|\leq k\leq Y+\left|x_{i}-x_{i-1}\right|}f_{i-1, k}+\sum\limits_{(x, y), x=x_{i}}|Y-y| $​​

同样,设 \(F_{i}(x)=f_{i, x}, G_{i}(x)=\sum\limits_{x-\left|x_{i}-x_{i-1}\right| \leq k \leq x+\left|x_{i}-x_{i-1}\right|} f_{i-1, k}\)​

那么 \(F_{i}(x)=G_{i}(x)+\sum_{\left(x^{\prime}, y^{\prime}\right), x^{\prime}=x_{i}}\left|x-y^{\prime}\right|\)


主要问题在于 \(G_i(x)\) 的维护,是取一个区间范围 \([L,R]\) 内的最小值。

若斜率为 \(0\) 的两端点在 \([L,R]\) 内,那么直接取最小值即可。

若斜率为 \(0\) 的两端点在 \(L\) 左侧,需要取 \(L\) 处的值作为最小值。

若斜率为 \(0\) 的两端点在 \(R\)​ 右侧,需要取 \(R\) 处的值作为最小值。

因而,需要维护斜率为 \(0\) 的折线的两侧分割点 \((a,b)\) ,同时还需要支持从斜率为 \(0\) 处向两侧访问,因而使用小根堆与大根堆分别维护 \(b\) 右侧以及 \(a\) 左侧的点。

每次添加新的分割点时,根据新分割点与 \(a,b\) 的大小关系决定插入小根堆or大根堆,同时调整 \(a,b\) ,每次调整复杂度是 \(\mathcal{O}(1)\) 的(从小根堆中取出塞入大根堆或反之)

【AC Code】借用 jiangly的代码

#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::pair<int, int>> a;
for (int i = 0; i < n; i++) {
int x, y;
std::cin >> x >> y;
a.emplace_back(x + y, x);
}
std::priority_queue<i64> hl;
std::priority_queue<i64, std::vector<i64>, std::greater<>> hr;
for (int i = 0; i < n + 5; i++) {
hl.push(0);
hr.push(0);
}
i64 tag = 0, mn = 0;
int last = 0;
std::sort(a.begin(), a.end());
for (auto [s, x] : a) {
int d = s - last;
last = s;
tag += d;
if (x <= hl.top()) {
mn += hl.top() - x;
hl.push(x);
hl.push(x);
hr.push(hl.top() - tag);
hl.pop();
} else if (x >= hr.top() + tag) {
mn += x - (hr.top() + tag);
hr.push(x - tag);
hr.push(x - tag);
hl.push(hr.top() + tag);
hr.pop();
} else {
hl.push(x);
hr.push(x - tag);
}
}
std::cout << mn << "\n";
return 0;
}

Slope Trick:解决一类凸代价函数DP优化的更多相关文章

  1. [笔记] Slope Trick:解决一类凸代价函数的DP优化问题

    原理 当序列 DP 的转移代价函数满足 连续: 凸函数: 分段线性函数. 时,可以通过记录分段函数的最右一段 \(f_r(x)\) 以及其分段点 \(L\) 实现快速维护代价的效果. 如:$ f(x) ...

  2. 重修 Slope Trick(看这篇绝对够!)

    Slope Trick 算法存在十余载了,但是我没有找到多少拍手叫好的讲解 blog,所以凭借本人粗拙的理解来写这篇文章. 本文除标明外所有图片均为本人手绘(若丑见谅),画图真的不容易啊 qwq(无耻 ...

  3. [总结]一些 DP 优化方法

    目录 注意本文未完结 写在前面 矩阵快速幂优化 前缀和优化 two-pointer 优化 决策单调性对一类 1D/1D DP 的优化 \(w(i,j)\) 只含 \(i\) 和 \(j\) 的项--单 ...

  4. DP 优化方法大杂烩 & 做题记录 I.

    标 * 的是推荐阅读的部分 / 做的题目. 1. 动态 DP(DDP)算法简介 动态动态规划. 以 P4719 为例讲一讲 ddp: 1.1. 树剖解法 如果没有修改操作,那么可以设计出 DP 方案 ...

  5. dp优化 | 各种dp优化方式例题精选

    前言 本文选题都较为基础,仅用于展示优化方式,如果是要找题单而不是看基础概念,请忽略本文. 本文包含一些常见的dp优化("√"表示下文会进行展示,没"√"表示暂 ...

  6. [提升性选讲] 树形DP进阶:一类非线性的树形DP问题(例题 BZOJ4403 BZOJ3167)

    转载请注明原文地址:http://www.cnblogs.com/LadyLex/p/7337179.html 树形DP是一种在树上进行的DP相对比较难的DP题型.由于状态的定义多种多样,因此解法也五 ...

  7. loj6171/bzoj4899 记忆的轮廊(期望dp+优化)

    题目: https://loj.ac/problem/6171 分析: 设dp[i][j]表示从第i个点出发(正确节点),还可以有j个存档点(在i点使用一个存档机会),走到终点n的期望步数 那么 a[ ...

  8. DP 优化方法合集

    0. 前言 写完这篇文章后发现自己对于 DP 的优化一窍不通,所以补了补 DP 的一些优化,写篇 blog 总结一下. 1. 单调队列/单调栈优化 1.2 算法介绍 这应该算是最基础的 DP 优化方法 ...

  9. 常见的DP优化类型

    常见的DP优化类型 1单调队列直接优化 如果a[i]单调增的话,显然可以用减单调队列直接存f[j]进行优化. 2斜率不等式 即实现转移方程中的i,j分离.b单调减,a单调增(可选). 令: 在队首,如 ...

随机推荐

  1. Linux学习之路第四天(运行级别)

    linux 实用指令 指定运行级别 运行级别说明 0 :关机 1:单用户(找回丢失密码) 2.多用户状态没有网络服务 3.多用户状态有网络服务 4.系统未保留给用户 5.图形界面 6.系统重启 常用的 ...

  2. 2021最新WordPress安装教程(二):安装PHP和MySQL

    这是 2021最新WordPress安装教程系列的第二篇文章,前一篇文章< 2021最新WordPress安装教程(一):Centos7安装Apache>已经完整的介绍了如何在Centos ...

  3. Python使用笔记20--网络操作小练习

    1 ''' 2 2.自己抓取qq群的接口,传入一个群号,然后把群成员的头像下载到本地,头像用群备注来命名,如果没有 3 群备注,那么取昵称. 4 ''' 5 import requests 6 imp ...

  4. Scrapy框架安装与使用(基于windows系统)

    "人生苦短,我用python".最近了解到一个很好的Spider框架--Scrapy,自己就按着官方文档装了一下,出了些问题,在这里记录一下,免得忘记. Scrapy的安装是基于T ...

  5. 详解API Gateway流控实现,揭开ROMA平台高性能秒级流控的技术细节

    摘要:ROMA平台的核心系统ROMA Connect源自华为流程IT的集成平台,在华为内部有超过15年的企业业务集成经验. 本文分享自华为云社区<ROMA集成关键技术(1)-API流控技术详解& ...

  6. MongoDB 基础学习

    1.MongoDB 概念解析 SQL术语/概念 MongoDB术语/概念 解释/说明 database database 数据库 table collection 数据库表/集合 row docume ...

  7. ubuntu18.04安装redis-desktop-manager

    通过proxychains4 clone项目,否则安装不成功 教程:https://www.cnblogs.com/bignode/p/9254500.html 1 git clone --recur ...

  8. python之数据驱动ddt操作(方法二)

    import unittestfrom ddt import ddt,unpack,datafrom selenium import webdriverfrom selenium.webdriver. ...

  9. 【爬虫系列】0. 无内鬼,破解前端JS参数签名

    PS:这是一个系列,坐等我慢慢填坑. PS:不太会直接能跑的代码,抛砖引玉. PS:那些我也不太熟练的就不搞了,包括(破滑块.验证码..) PS: 反编译搞Apk会有很长的几个文章,稍后慢慢更. 最近 ...

  10. CF896D Nephren Runs a Cinema

    CF896D Nephren Runs a Cinema 题意 售票员最开始没有纸币,每次来一个顾客可以给她一张.拿走她一张或不操作.求出不出现中途没钱给的情况 \(n\) 名顾客后剩余钱数在 \(l ...