题意

第一行输入三个整数 \(n,m,d(1 \leq n \leq 2 * 10^5, n \leq m \leq 10^9, 1 \leq d \leq n)\),第二行输入 \(n\) 个整数,保证每个数均不大于 \(m\)。

在每一天你都可以任意选择一个未选过的数 \(a_i\),随后可以继续选任意一个大于 \(a_i + d\) 的数 \(a_j\);接下来可以再选任意一个大于 \(a_j + d\) 的数 \(a_k\);最后重复执行上述步骤。

问:最少需要多少天,可以选完全部的数?

题解

先给结论:先从小到大排序,然后选中剩下的数组的第一个数 \(a_0\),随后选中数组中第一个满足 \(a_i \geq a_0 + d + 1\) 的数 \(a_i\),下一步再选中数组中第一个满足 \(a_j \geq a_i + d\) 的数 \(a_j\),形成的子数组数目便是答案。

疑问解析:为什么优先选择最小的满足 \(a_j \geq a_i + d + 1\)的数是正确的?

答:不妨假设数组排序后为 \(a_0, a_1, ..., a_i, a_j, ..., a_n\),且 \(a_i\) 是最小的满足大于 \(a_0 + d\) 的数。那么明显在选中 \(a_0\) 的下一步,既可以选择 \(a_i\),也可以选择 \(a_j\)。此时若 \(a_j - a_i \leq d\),那明显二者至多只能够选其一,那么无论选择哪一个,都必然意味着需要另外再选择出一个子数组。但如果 \(a_j - a_i \geq d + 1\),就可以先选 \(a_i\) 而后一并选上 \(a_j\)。因此,先选较小者更优。

接下来分析具体代码实现:

如何在数组中找出第一个比选中的数大 \(d\) 以上的数呢?

明显可以线性查找,但是当数组中任意两个数的差值均小于 \(d\) 时,时间复杂度为 \(O(n^2)\),明显不符合要求

观察我们的前置条件排序,所以可以考虑使用二分法来使得查找的时间复杂度降低为 \(O(logn)\)

如何选择过的数从数组中移除呢?

若通过类似插入排序等思路,时间复杂度为 \(O(n^2)\),明显不符合要求

此时思考:既要排序,又要可以二分,还要支持快速删除,我们可以联想到红黑树的性质,但是手撕红黑树太硬核了,可以借助 \(set\) 或 \(map\) 实现,这二者的查找/插入/删除/修改操作时间复杂度都是 \(O(logn)\),符合时间复杂度要求。

参考代码

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
using namespace std; constexpr int INF = 0x3f3f3f3f;
constexpr int N = 2e5 + 7;
int n, m, d, cnt, a;
int ans[N]; int main() {
IOS
set<PII> st;
cin >> n >> m >> d;
for (int i = 0; i < n; ++ i) {
cin >> a;
st.insert(PII(a, i));
}
while (!st.empty()) {
auto f = st.begin();
int key = (*f).first;
ans[(*f).second] = ++ cnt;
st.erase(f);
while (!st.empty()) {
auto ne = st.upper_bound(PII(key + d, INF));
if (ne != st.end()) {
key = (*ne).first;
ans[(*ne).second] = cnt;
st.erase(ne);
} else break;
}
}
cout << cnt << '\n';
for (int i = 0; i < n; ++ i) cout << ans[i] << ' ';
return 0;
}

【巧用set实现对有序数组O(logn)时间复杂度增、删、查、改、二分操作】codeforces 1041 C. Coffee Break的更多相关文章

  1. [LeetCode]105. 从前序与中序遍历序列构造二叉树(递归)、108. 将有序数组转换为二叉搜索树(递归、二分)

    题目 05. 从前序与中序遍历序列构造二叉树 根据一棵树的前序遍历与中序遍历构造二叉树. 注意: 你可以假设树中没有重复的元素. 题解 使用HashMap记录当前子树根节点在中序遍历中的位置,方便每次 ...

  2. iOS常用算法之两个有序数组合并, 要求时间复杂度为0(n)

    思路: 常规思路: 先将一个数组作为合并后的数组, 然后遍历第二个数组的每项元素, 一一对比, 直到找到合适的, 就插入进去; 简单思路: 设置数组C, 对比A和B数组的首项元素, 找到最小的, 就放 ...

  3. 使用C语言封装数组,动态实现增删改查

    myArray.h : #pragma once //包含的时候只包含一次 #include <stdio.h> #include <stdlib.h> #include &l ...

  4. 【算法】字典的诞生:有序数组 PK 无序链表

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  5. 两个有序数组的上中位数和第K小数问题

    哈,再介绍个操蛋的问题.当然,网上有很多解答,但是能让你完全看懂的不多,即便它的结果是正确的,可是解释上也是有问题的. 所以,为了以示正听,我也做了分析和demo,只要你愿意学习,你就一定能学会,并且 ...

  6. 【算法】实现字典API:有序数组和无序链表

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  7. [LeetCode] Find Minimum in Rotated Sorted Array 寻找旋转有序数组的最小值

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...

  8. [LeetCode] Convert Sorted Array to Binary Search Tree 将有序数组转为二叉搜索树

    Given an array where elements are sorted in ascending order, convert it to a height balanced BST. 这道 ...

  9. [LeetCode] Merge Sorted Array 混合插入有序数组

    Given two sorted integer arrays A and B, merge B into A as one sorted array. Note:You may assume tha ...

  10. [LeetCode] Remove Duplicates from Sorted Array 有序数组中去除重复项

    Given a sorted array, remove the duplicates in place such that each element appear only once and ret ...

随机推荐

  1. [Tkey] CodeForces 1267G Game Relics

    太神了这题,膜拜出题人 orz. 思考一 首先是大家都提到的一点,先抽卡再买.这里来做个数学分析. 假设我们还剩 \(k\) 种没有买,其实我们是有式子来算出它的花费期望的.WIKI 上提到,假设一个 ...

  2. mysql-存储过程(1) mysql循环语句

    mysql循环语句: 本文总结了mysql常见的三种循环方式:while.repeat和loop循环.还有一种goto,不推荐使用. 一.while循环 delimiter // #定义标识符为双斜杠 ...

  3. Android复习(四)权限—>概览

    权限概述 许可 的目的是保护Android用户的隐私.Android应用必须获得访问敏感用户数据(例如联系人和SMS)以及某些系统功能(例如相机和互联网)的权限.根据功能的不同,系统可能会自动授予权限 ...

  4. 技术分享PPT整理(三):网页渲染流程

    在我刚开始学习Web开发的时候,一直有个疑问--我写出的代码究竟是在什么时候发生作用的呢?是不是每次我修改代码网页都随之变化了?当然,现在来看这肯定是一个错误的想法,经过一段时间的工作和学习后,代码到 ...

  5. 5.15 相约上海!2021 年度首届云原生 Meetup | KubeSphere & Friends

    时至今日,Kubernetes 虽然变成了云原生这套系统化方法论和开源技术的核心一环,但已经无法独立存在,而是与云原生生态中所有的技术形态息息相关.为了将云原生生态中的各个技术形态结合起来,帮助企业最 ...

  6. Redis的发布订阅Pub/Sub

    发布订阅 Redis 发布订阅(publish/subscribe)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. Redis 客户端可以订阅任意数量的频道. 下图展示了频道 ...

  7. 一文彻底弄懂spring boot自动转配的过程

    Spring Boot 的自动配置机制是它的重要特性之一,极大地简化了 Spring 应用的配置工作.自动配置的核心思想是基于类路径中的依赖.环境配置以及自定义代码进行智能化配置,避免了开发者手动编写 ...

  8. VictoriaMetrics 中文教程(10)集群版介绍

    VictoriaMetrics 中文教程系列文章: VictoriaMetrics 中文教程(01)简介 VictoriaMetrics 中文教程(02)安装 VictoriaMetrics 中文教程 ...

  9. vscode连接docker时需要为docker容器开设一个映射端口

    相关: vscode远程连接远程主机上的docker -- 设置命令 -- -p 5001:5001 设置端口: -p 5001:5001 命令Demo: docker run -it -v /hom ...

  10. 循环中拼接String不同方法性能耗时对比

    对比背景 Java中最常用的拼接字符串方法就是 + 或 +=,使用上简单方便.但如果拼接数量比较大,例如在循环中拼接字符串,可能会有性能问题: 测试数据 循环100000次进行String拼接,对比+ ...