题目链接
题目大意
给出一个集合(允许元素重复),大小为 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 x≥minn,那么 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 <bits/stdc++.h>
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;
}