传送门

前言

本题是一道很好的“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. 一个.NET开源、快速、功能丰富的跨平台阅读服务器

    前言 今天大姚给大家分享一个基于.NET开源的快速.功能丰富的跨平台阅读服务器,它的设计初衷是提供一个全面的解决方案,满足用户的所有阅读需求.用户可以设置自己的服务器,并与朋友和家人分享阅读收藏:Ka ...

  2. ElementUI Select单选切换多选无法清除历史数据的解决方案

    背景: 有一个tab切换,每一个tab下都有一个 下拉框,只是一个是多选一个是单选,问题是当切换tab标签的时候,下拉框的样式不会被清空. 解决方案: 只需要在 el-select 上加一个 key ...

  3. python多版本管理软件pyenv

    我们在平时的项目开发或者学习中,有可能使用不同的Python版本,大家都知道Python的版本非常多,如果我们把需要的不同版本的Python都下载到服务器上,管理起来会非常困难,多版本并存又容易互相干 ...

  4. API接口之设计篇

    在实际工作中,我们需要经常跟第三方平台打交道,可能会对接第三方平台API接口,或者提供API接口给第三方平台调用. 那么问题来了,如果设计一个优雅的API接口,能够满足:安全性.可重复调用.稳定性.好 ...

  5. PHP之环境搭建(php7.4 + php8.1)

    之前写过几次,使用lnmp,宝塔,源码编译等方式来进行PHP环境的搭建, 随着接触的越来越多, 这里做一个总结, 常用的搭建方式 1.编译安装 之前写个几次,可以参考之前的 这次记录下多个版本PHP的 ...

  6. SPRING 动态注册BEAN

    场景 有些情况下,不能直接使用BEAN的方式: @Bean(name = "storage") public DataSourceProxy storageDataSourcePr ...

  7. uniapp 使用pinpa 持续化更新

    安装依赖 npm i pinia npm i pinia-plugin-persistedstate 新建 index.ts import { createPinia } from 'pinia' i ...

  8. AE对象序列化

    当我们编写AE程序时,通常会遇到需要存储某个AE对象的情况,比如Layer,Element,Map,Legend,NorthArrow等等这些.举个例子说明一下:在我们编辑Featurelayer时, ...

  9. uni-app项目button组件去不掉的灰色边框爬坑

    前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验非常棒,公司项目就是主推uni-app. 坑位 最近在开发个人 ...

  10. Winserver主副域控切换的方法

    ​查看当前的dc netdom query dc 步骤 登录主域控(PDC),例如主域控的主机名为dc01.yourdomaincontroller.com (FQDN 格式) 打开CMD命令行终端, ...