【二分】CF1354D Multiset

传送门

题目描述

注意,本题的内存限制不同于常规题目。

给定一个包含 n n n 个整数的多重集。你需要处理两种类型的操作:

  • 向多重集中添加整数 k k k;
  • 查询并删除多重集中第 k k k 小的数。

多重集中的第 k k k 小数,指的是将多重集中的所有元素从小到大排序后,第 k k k 个元素。例如,如果多重集包含元素 1 1 1、 4 4 4、 2 2 2、 1 1 1、 4 4 4、 5 5 5、 7 7 7,且 k = 3 k=3 k=3,那么排序后为 [ 1 , 1 , 2 , 4 , 4 , 5 , 7 ] [1, 1, 2, 4, 4, 5, 7] [1,1,2,4,4,5,7],第 3 3 3 小的数是 2 2 2。如果要删除的元素在多重集中出现多次,仅删除其中一个。

在所有操作完成后,输出多重集中的任意一个数。如果多重集为空,输出 0 0 0。

输入格式

第一行包含两个整数 n n n 和 q q q( 1 ≤ n , q ≤ 10 6 1 \le n, q \le 10^6 1≤n,q≤106),分别表示初始多重集中的元素个数和操作次数。

第二行包含 n n n 个整数 a 1 , a 2 , ... , a n a_1, a_2, \ldots, a_n a1,a2,...,an( 1 ≤ a 1 ≤ a 2 ≤ ⋯ ≤ a n ≤ n 1 \le a_1 \le a_2 \le \dots \le a_n \le n 1≤a1≤a2≤⋯≤an≤n),表示多重集中的元素。

第三行包含 q q q 个整数 k 1 , k 2 , ... , k q k_1, k_2, \ldots, k_q k1,k2,...,kq,每个整数代表一个操作:

  • 如果 1 ≤ k i ≤ n 1 \le k_i \le n 1≤ki≤n,则第 i i i 个操作为"将 k i k_i ki 插入多重集";
  • 如果 k i < 0 k_i < 0 ki<0,则第 i i i 个操作为"删除多重集中第 ∣ k i ∣ |k_i| ∣ki∣ 小的数"。对于此操作,保证 ∣ k i ∣ |k_i| ∣ki∣ 不大于当前多重集的大小。

输出格式

如果所有操作后多重集为空,输出 0 0 0。

否则,输出多重集中任意一个元素。

输入输出样例 #1

输入 #1

复制代码
5 5
1 2 3 4 5
-1 -1 -1 -1 -1

输出 #1

复制代码
0

输入输出样例 #2

输入 #2

复制代码
5 4
1 2 3 4 5
-5 -1 -3 -1

输出 #2

复制代码
3

输入输出样例 #3

输入 #3

复制代码
6 2
1 1 1 2 3 4
5 6

输出 #3

复制代码
6

说明/提示

在第一个样例中,多重集中的所有元素都被删除了。

在第二个样例中,元素 5 5 5、 1 1 1、 4 4 4、 2 2 2 被依次删除。

在第三个样例中, 6 6 6 不是唯一的答案。

由 ChatGPT 4.1 翻译


题解

wakakaka,没想到时隔n年上大学又来写题解了qwq

一开始以为是线段树,一看内存估计是超了

暴力按照题目要求模拟,时间应该是 O ( n 3 ) O(n^3) O(n3)

后面想到既然只是看有没有数最后还在,那不是只要遍历一遍数集,判断每个数有没有被前后删掉不就好了,可是 O ( n 2 ) O(n^2) O(n2)肯定还是超时的

这时仔细阅读题目发现,这个数集里的数都是1到n的,所以不管什么大小排序或者何时加入进来的,最后剩下来的数本质就是一个在1到n的数

其实根本不用管数集里面那些的"数字个体",而是判断一个在1到n的数最后有没有留下来就可以了

二分答案

check(mid)判断mid有没有被前删

记录≤mid的数的数量,如果被从前面删掉了,那么比mid小的数肯定都会被删掉,所以答案肯定就在mid后了

以此为思路找到最小的那个没被删掉的数


Code

cpp 复制代码
#include <bits/stdc++.h>
#define N 1000010

using namespace std;

int n, m, l, r, mid, ans;
int a[N], q[N];

bool check(int x) {
	int qian = 0;
	for(int i = 1; i <= n; i ++)
		if(a[i] <= x) qian ++;
	for(int i = 1; i <= m; i ++)
		if(q[i] < 0) {
			if(-q[i] <= qian) qian --;
		} else 
			if(q[i] <= x) qian ++;
	return (qian);
}

int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i ++)
		scanf("%d", &a[i]);
	int ls = n;
	for(int i = 1; i <= m; i ++) {
		scanf("%d", &q[i]);
		if(q[i] < 0) ls --; else ls ++;
	}
	if(!ls) {
		printf("0");
		return 0;
	}
	l = 1, r = n;
	while(l <= r) {
		mid = (l + r) >> 1;
		if(check(mid)) {
			ans = mid;
			r = mid - 1;
		} else l = mid + 1;
	}
	printf("%d", ans);
}
相关推荐
一只小bit2 小时前
Qt MainWindow:主窗口组件的介绍与正确使用
前端·c++·qt
我是一只小青蛙8882 小时前
C++核心过渡:类与对象精讲
开发语言·c++
玖釉-2 小时前
Windows 下 VS2022 编译运行 Khronos Vulkan Samples 全避坑指南
c++·windows·图形渲染
星火开发设计2 小时前
C++ 分支结构:if-else 与 switch-case 的用法与区别
开发语言·c++·学习·算法·switch·知识·分支
txzrxz2 小时前
数据结构有关的题目(栈,队列,set和map)
数据结构·c++·笔记·算法··队列
Two_brushes.2 小时前
C++ 常见特殊类的设计(含有单例模式)
开发语言·c++
CoderCodingNo3 小时前
【GESP】C++五级练习题(前缀和) luogu-P1114 “非常男女”计划
数据结构·c++·算法
阿班d3 小时前
33333333
c++
charlee443 小时前
C++ 封装 C FFI 接口最佳实践:以 Hugging Face Tokenizer 为例
c++·智能指针·tokenizer·ffi·raii