传送门

前言

本题是一道很好的“dp”题,无论是正难反易,还是模型转化都值得称赞,尤其是最后的神之一手,让我大脑宕机。

题意描述

给定一个长度为 \(N\) 的序列 \(H\),修改不超过 \(K\) 个数,使得 \(\max_{1}^{N - 1}{H_{i + 1} - H_i}\) 最小。

\(2 \le N \le 2 \times 10^5\) ,\(0 \le K \le N\) ,\(1 \le H_i \le 10^9\)

思路推导 & 做法

首先,对于这一类最大值最小的问题,我们有一个模板化的思考方向——二分答案,在尝试后,发现对于该题在确定答案后再判断是否有解是有用的,因为答案限制了相邻 \(H_i\) 的取值,同时并没有什么可贪心或能转化为图论的地方,所以考虑 \(\text{dp}\) check。

然后怎么做呢?在思考良久后,发现这道题很难(这不废话吗),连暴力 \(\text{dp}\) 都打不出来。直接硬做做不出来,就要思考如何把题目进行转化以降低难度。此时就要发挥你惊人的注意力,像瞪几何大题一样敏锐地发现直接做做不出来的原因在于若该修改 \(i\) 你在 \(i + 1\) 就不知道上一个选了什么数也无法定义进状态里,所以就要把从某个地方转移过来的数固定下来,所以就要将“不修改”放进 \(\text{dp}\) ,也就是正难反易,于是定义 \(dp_i\) 表示考虑完前 \(i\) 座山丘,第 \(i\) 座山丘不修改,最多能有多少座山丘不修改。

考虑状态转移,设二分的答案为 \(danger\),若 \(2\) 座山丘 \(i, j (j < i)\) 都不修改,则要满足对于山丘 \(k \in (j, i)\) 都修改的情况下,也就是最容易满足的情况下能满足条件,即 \(|H_i - H_j| \le danger \times (i - j)\) 。所以有如下 \(\text{dp}\) 转移式:

\[dp_i = \max_{j \in [1, i) \wedge |H_i - H_j| \le danger \times (i - j)}{dp_j + 1}
\]

目前我们有了 \(O(N^2 \log {10^9})\) 的做法,但这明显不够,所以我们要把 \(\text{dp}\) 优化进 \(O(N \log N)\) 及以内。

现在我们要从状态转移方程入手,发现其中最不规整也最难优化的是 \(|H_i - H_j| \le danger \times (i - j)\) 这一个条件,绝对值的存在让我们不能轻易优化,所以要拆绝对值,条件就变成了

\[H_i - H_j \le danger \times (i - j) \wedge H_j - H_i \le danger \times (i - j)
\]

再将具有同一变量的值移到同侧,变为

\[danger \times j - H_j \le danger \times i - H_i \wedge danger \times j + H_j \le danger \times i + H_i
\]

发现所有与 \(j\) 有关的和与 \(i\) 有关的都分列两侧,所以可以另定义权值,把条件变漂亮。定义 \(X_i = danger * i\) ,定义 \(v_i \ge v_j\) 为 \(X_i - H_i \ge X_j - H_j 且 X_i + H_i \ge X_j + H_j\) (这式子好整齐啊,要不是我推出来的一定还有转化),反之为 \(\le\) ,问题就转化为给定长度为 \(n\) 的序列 \(v\) ,求该序列的最长不下降子序列

当你做到这一步时大抵是会欣喜若狂像我一样 ,以为马上就切掉这道题了,但好题就是好题,总在你得意忘形时给你沉重一击(笑容凝固)。你惊世骇俗地发现加上 \(i\) 这一维下标后就变成了三维偏序,解决的经典办法是CDQ分治或二维树状数组,但 \(O(N \log^2 N)\),总时间 \(O(N \log^2 N log 10^9)\) ,然后你的动作be like:Win + R ——calc——\(200000 \times (\frac{log_{10}^{200000}}{log_{10}^{2}})^2 \times \frac{log_{10}^{10^9}}{log_{10}^{2}} = 1854230461.3827186693864795412925\dots\) ,随即亲切地问候了出题者的祖宗苦思冥想,始终不得其解。

神之一手

我翻开算法圣经(蓝本)一查,这三维偏序没有界限,歪歪斜斜的每页上都写着CDQ分治几个字。我横竖想不通,仔细看了半日,才从字缝里看出来,满本都写着两个字是 \(O(N \log^2 N)\)!

当你接近崩溃的时候,你忽地想起了推出的式子和心中的想法,抱着试一试的心态去推了一下式子进行转化(回收伏笔),脑袋里还想着什么高深算法,把三减个一变成二,突然发现答案就在笔下。

若 \(v_j \ge v_i (j < i)\) ,则

\[X_j - H_j \ge X_i - H_i
\]
\[X_j + H_j \ge X_i + H_i
\]

变形得

\[H_i - H_j \ge X_i - X_j
\]
\[H_i - H_j \le X_j - X_i
\]

将 \(X_i,X_j\) 还原回去

\[H_i - H_j \ge danger \times (i - j) > 0
\]
\[H_i - H_j \le danger \times (j - i) < 0
\]
\[矛盾!
\]

所以我们可以得出若 \(j < i\) ,\(v_j \le v_i\) ,所以当交换 \(v_j, v_i\) 后(权值不变),\(dp_i\) 一定不对 \(dp_j\) 产生贡献,所以交换两个数后答案一定不会变大。

若 \(v_i \ge v_j (j < i)\) ,则

\[X_i - H_i \ge X_j - H_j
\]
\[X_i + H_i \ge X_j + H_j
\]

由此得出如果按某一维排序后 \(dp_j\) 仍然在 \(dp_i\) 前且一定满足 \(v_i \ge v_j\) ,\(dp_i\) 仍然可以从 \(dp_j\) 转移,所以原本可转移的方向现在仍然存在,故排序后答案不会变小。

交换 \(v_i, v_j\) 后答案不变大又可以缩小范围为对 \(v\) 排序后答案不变大,此时答案也不变小,所以任意按某一维排序后答案不变!!!

这神之一手直接把索引 \(i\) 的一维砍掉,成功把三维偏序转化为二维偏序,直接用 \(\text{LIS}\) 模板树状数组即可。

solution

思路想通后代码异常简单。

/*
address:https://dmoj.ca/problem/utso21p6
AC 2025/1/11 16:49
*/
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & -x)
const int N = 2e5 + 5;
int n, k;
int h[N];
struct BinaryTree {
int c[N];
inline void init(int n) { fill(c + 1, c + n + 1, 0); }
inline void change(int x, int k) { for (;x <= n;x += lowbit(x)) c[x] = max(c[x], k); }
inline int query(int x) {
int ret = 0;
for (;x > 0;x -= lowbit(x)) ret = max(ret, c[x]);
return ret;
}
}BIT;
typedef long long LL;
pair<LL, LL>a[N];
LL disc[N];
int dp[N];
inline bool check(int danger) {
BIT.init(n + 1);
for (int i = 1;i <= n;i++) a[i] = { 1ll * danger * i - h[i],1ll * danger * i + h[i] };
sort(a + 1, a + n + 1);
for (int i = 1;i <= n;i++) disc[i] = a[i].second;
sort(disc + 1, disc + n + 1);
int m = unique(disc + 1, disc + n + 1) - disc - 1;
for (int i = 1;i <= n;i++) a[i].second = lower_bound(disc + 1, disc + m + 1, a[i].second) - disc;
for (int i = 1;i <= n;i++) {
dp[i] = BIT.query(a[i].second) + 1;
BIT.change(a[i].second, dp[i]);
}
for (int i = 1;i <= n;i++)
if (dp[i] >= n - k) return true;
return false;
}
int main() {
int T;scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
for (int i = 1;i <= n;i++) scanf("%d", &h[i]);
int l = 0, r = 1e9, ans = 1e9;
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) r = mid - 1, ans = mid;
else l = mid + 1;
}
printf("%d\n", ans);
}
return 0;
}

总结

这道题单说前几步难度就已经很大了,最后出题者的阴险巧妙构思画龙点睛,让这道题的难度更上一层。这种巧妙的题还是少见,值得珍惜。

同时也明白了莫要

只言片语尽显高见, 行动却似矮人观场

UTS Open '21 P6 - Terra Mater的更多相关文章

  1. P6 EPPM 安装与配置指南 16 R1 2016.4

       关于安装和 配置P6 EPPM 本指南告诉你如何自动 安装和配置您的应用程序. 在您开始之前,阅读 先决条件 P6 EPPM配置 (7页). 安装P6 EPPM 您将使用 安装程序 (窗口) . ...

  2. P6 EPPM手动安装指南(Oracle数据库)(一)

    P6 EPPM手动安装指南(Oracle数据库) P6 EPPM Manual Installation Guide (Oracle Database) 1.      内容... 1 1.1.    ...

  3. P6 EPPM Manual Installation Guide (Oracle Database)

    P6 EPPM Manual Installation Guide (Oracle Database) P6 EPPM Manual Installation Guide (Oracle Databa ...

  4. P6 EPPM Installation and Configuration Guide 16 R1 April 2016

    P6 EPPM Installation and Configuration Guide 16 R1         April 2016 Contents About Installing and ...

  5. P6 Professional Installation and Configuration Guide (Microsoft SQL Server Database) 16 R1

    P6 Professional Installation and Configuration Guide (Microsoft SQL Server Database) 16 R1       May ...

  6. 面试阿里前端P6血和泪换来的收获

      我的一个朋友在前端耕耘一段时间,也在网上进行了高度培训学习,最近一段时间他打算跳槽去阿里面试前端P6开发岗位,结果被痛虐了一回,估计从此以后会给他留下不可磨灭的阴影啊 真是十年生死两茫茫,一鲁代码 ...

  7. Linux ns 4. UTS Namespace 详解

    目录 1. 使用简介 1.1 hostname 1.2 domainname 1.3 uname 2. 代码分析 2.1 copy_utsname() 2.2 sethostname() 2.3 ge ...

  8. 【夯实Mysql基础】MySQL性能优化的21个最佳实践 和 mysql使用索引

    本文地址 分享提纲: 1.为查询缓存优化你的查询 2. EXPLAIN 你的 SELECT 查询 3. 当只要一行数据时使用 LIMIT 1 4. 为搜索字段建索引 5. 在Join表的时候使用相当类 ...

  9. 2-1 Linux 操作系统及常用命令

    根据马哥linux初级视频 2-1.2-2来编辑 1. GUI与CLI GUI: Graphic User Interface CLI: Command Line Interface 注:在Windo ...

  10. Fedora 21 安装 Nvidia 驱动以及失败后的补救方法

    在 Linux 桌面系统下玩了这么久,大部分时间都是使用 Ubuntu,偶尔使用一下 Fedora.我的电脑中安装有多个 Linux 发行版,见这里<在同一个硬盘上安装多个Linux发行版及Fe ...

随机推荐

  1. P4629 SHOI2015 聚变反应炉

    P4629 SHOI2015 聚变反应炉 树上背包+树形dp. 算是套娃题吗? 思路 看到数据考虑数据分治. part1 贪心 \(c_i\leq 1\) 对于这种情况,我们考虑贪心的点亮. 手玩几组 ...

  2. ArcGIS填补面图层的细小空白并删除主体部分外的零散部分

      本文介绍在ArcMap软件中,基于消除面部件("Eliminate Polygon Part")工具,对矢量面要素的零碎.空洞区域加以删除,同时将游离于要素主体之外的部分剔除的 ...

  3. think in java interview-高级开发人员面试宝典(二)

    从现在开始,以样题的方式一一列出各种面试题以及点评,考虑到我在前文中说的,对于一些大型的外资型公司,你将会面临全程英语面试,因此我在文章中也会出现许多全英语样题. 这些题目来自于各个真实的公司,公司名 ...

  4. 创建一个具有商业品质的 Eclipse IDE

    创建具有商业品质且可插入 Eclipse 的专业 IDE Prashant Deva (pdeva@placidsystems.com), 创始人, Placid Systems 简介:  " ...

  5. 销讯通CRM系统如何管理医药代表的销售过程

    医药行业的销售代表与其他行业的销售代表在专业知识要求.客户群体.销售流程.以及行业特性等方面都存在明显的区别,他们必须具备更高的专业素养和综合能力. CRM(客户关系管理系统)在医药行业中对于管理医药 ...

  6. NATS: 对象存储

    https://natsbyexample.com/examples/os/intro/dotnet2 NATS 中的对象存储能力是在 Stream 之上的一种抽象,它将消息的主题视为类似键值对中的键 ...

  7. 金Gien乐道 | 8月热点回顾

    中电金信2023年中工作会议:加快推进以"源启"为基石的业务模式转型 ​​8月12日,中电金信2023年中工作会议在上海召开.会议贯彻落实中国电子2023年中工作会议精神,总结上半 ...

  8. docker.sock: connect: permission denied 解决

    问题描述xjun@DESKTOP-L2R4GKN:~$ docker run -it hello-worlddocker: Got permission denied while trying to ...

  9. Qt音视频开发12-easyplayer内核

    一.前言 在视频监控行业经常看到两个厂家广告打得比较厉害,一个是青犀视频对应easyplayer,一个是大牛直播,两个最初都是sdk免费,并提供调用示例源码,后面大牛直播的sdk以及示例都无法运行,目 ...

  10. Qt编写物联网管理平台39-报警联动

    一.前言 本系统支持报警联动,就是某个探测器报警后,再去下发命令,通知下面的继电器警号,一般是通过串口发送,由于现场会利用现有的串口线路比如485总线,所以本系统需要做特殊处理,就是公用485通信总线 ...