Educational Codeforces Round 87 D. Multiset(二分)

题目链接

传送门

题目大意

给出一个集合(允许元素重复),大小为 n n n。有两种操作:

  • 添加一个元素 k k k到这个集合
  • 从集合中删除第 k k k小的元素

根据输入执行完所有的操作后,输出任意一个存在于集合中的元素,如果集合为空,则输出 0 0 0

题目分析

第一种做法:用数据结构:权值线段树 o r or or 平衡树 o r or or 树状数组,但是可能会超时,因为 n n n的范围: [ 1 , 1 0 6 ] [1,10^6] [1,106]

第二种做法:因为题目只要求:输出一个元素。所以,我们尝试寻找:经过一系列操作后,在集合中的最小元素。方法:二分这个最小元素的值 m i n n minn minn

二分:区间: l = 0 , r = n + 1 l=0,r=n+1 l=0,r=n+1 m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2。对于一个数 x x x,如果 x ≥ m i n n x \ge minn xminn,那么 x x x通过二分的 c h e c k check check,否则不通过。进一步分析:如果 m i d mid mid通过二分的 c h e c k check check,那么答案等于 m i d mid mid或者比 m i d mid mid更小,所以 r = m i d r=mid r=mid;如果 m i d mid mid没通过二分,则说明答案比 m i d mid mid大,或者集合为空,所以 l = m i d + 1 l=mid+1 l=mid+1

c h e c k check check:把集合分为两部分:一部分小于等于 x x x,另一部分大于 x x x,分别得到两个部分的数量 c n t 1 , c n t 2 cnt1,cnt2 cnt1,cnt2。接下来扫描一遍操作,对 c n t 1 , c n t 2 cnt1,cnt2 cnt1,cnt2进行相应的修改就可以了。 c h e c k check check不通过条件:在操作中途,第一部分的数量 c n t 1 < 0 cnt1 < 0 cnt1<0。或者,结束完全部操作后,第一部分的数量 c n t 1 = 0 cnt1=0 cnt1=0

注意,要处理好集合为空的情况

代码

#include 
using namespace std;
const int maxn = 1e6+5;
int n, q;
int a[maxn];
int k[maxn];

bool check(int x) {
	int cnt1 = 0, cnt2 = 0;
	for(int i = 1; i <= n; i++) {
		if(a[i] <= x) cnt1++;
		else cnt2++;
	}
	
	for(int i = 1; i <= q; i++) {
		if(k[i] < 0) {
			if(-k[i] <= cnt1) cnt1--;
			else cnt2--;
		}
		else {
			if(k[i] <= x) cnt1++;
			else cnt2++;
		}
		
		if(cnt1 == -1) return false;
	}
	
	if(cnt1 == 0) return false;
	else return true;
}

int main() {
	scanf("%d %d", &n, &q);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for(int i = 1; i <= q; i++) scanf("%d", &k[i]);
	
	int l = 0, r = n+1;
	while(l < r) {
		int mid = (l+r)/2;
		if(check(mid)) r = mid;
		else l = mid+1;
	}
	
	if(l == n+1) printf("0\n");
	else printf("%d\n", l);
	return 0;
}

你可能感兴趣的:(cf题解)