一、解题思路
题意:题目给定三种操作:
0 x
表示把x
插入容器 ;
1 x
表示删除一个x
如果没有x
则输出 No Elment!
;
2 a k
表示比a
大的数中的第k
大的数 如果没有输出No Find!
思路:
树状数组维护元素出现次数(权值)前缀和即可。
操作\(0\)即修改;
操作\(1\)先查询\(x\)是否存在,如果存在删除一个即可。
操作\(2\)可以用二分逐渐逼近答案。
二、简单二分
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
typedef long long LL;
using namespace std;
#define lowbit(x) (x & -x)
const int M = 100010, N = 100000;
int t[M];
//小于等于v的元素的数目
int getsum(int x) {
int s = 0;
while (x) s += t[x], x -= lowbit(x);
return s;
}
//将值为x的元素增加v个
void update(int x, int v) {
while (x <= N) t[x] += v, x += lowbit(x);
}
int main() {
int m;
while (~scanf("%d", &m)) {
memset(t, 0, sizeof(t));
while (m--) {
int op;
scanf("%d", &op);
if (op == 0) {
int x;
scanf("%d", &x);
update(x, 1);
} else if (op == 1) {
int x;
scanf("%d", &x);
if (getsum(x) - getsum(x - 1) == 0)
printf("No Elment!\n");
else
update(x, -1);
} else {
int a, k;
scanf("%d %d", &a, &k);
int x = getsum(a);
if (getsum(N) - x < k)
printf("Not Find!\n");
else {
int l = a, r = N;
while (l < r) {
int mid = (l + r) >> 1;
if (getsum(mid) - x >= k)
r = mid;
else
l = mid + 1;
}
printf("%d\n", l);
}
}
}
}
return 0;
}
三、以二进制思想查找第\(k\)大的数
首先树状数组\(t[i]\)里面存的是在\(i\)管辖的范围内各个组数的和,比如\(1\)出现\(2\)次,\(2\)出现\(3\)次,\(4\)出现\(6\)次:
那么\(a[1]=2,a[2]=3,a[3]=0,a[4]=6\);故\(t[4]=11\);所以总的元素如下\(\{1,1,2,2,2,4,4,4,4,4,4\}\)。
知道了\(a[]\)数组的含义后(\(a[x]\)表示\(x\)出现的次数),我们再来看看树状数组求和的过程:
假设我们要求\(sum[15] (a[1] + …… + a[15])\),根据树状数组巧妙的求和运算,
依次累加\(t[1111\)(二进制表示)\(],t[1110],t[1100],t[1000]\)
反过来看,利用二进制,从高位到地位确定当前位是\(1\)还是\(0\),首先假设是\(1\),判断累计结果是否会超过\(k\),超过\(k\)则假设不成立,应为\(0\),继续确定下一位。基于二进制最后可以确定第\(k\)小的数的数值。
(注意:利用树状数组求第\(k\)小的元素的时候是根据总的元素的,例如上面举例的{\(1,1,2,2,2,4,4,4,4,4,4\)},而不是在\(a[1],a[2],a[3],a[4]\)中去寻求第\(k\)小,并且求第\(k\)小时候包含重复元素的数,没有直接跳过重复元素,例如还是上面的例子第\(2\)小还是\(1\)。
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <queue>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
#define lowbit(x) (x & -x)
const int N = 100010;
int t[N];
//小于等于v的元素的数目
int getsum(int x) {
int s = 0;
while (x) s += t[x], x -= lowbit(x);
return s;
}
//将值为x的元素增加v个
void update(int x, int v) {
while (x <= N) t[x] += v, x += lowbit(x);
}
//寻找第k大的模板
int find_kth(int k) {
int ans = 0, sum = 0;
for (int i = 20; i >= 0; i--) { //这里的20适当的取值,与MAX_VAL有关,一般取lg(MAX_VAL)
ans += (1 << i); //答案一定可以表示成二进制数的形式
if (ans >= N || sum + t[ans] >= k) //寻找小于k的最大的ans
ans -= (1 << i);
else
sum += t[ans];
}
return ans + 1; // ans是小于k的最大的数 那么ans+1必定是第k大的数
}
int main() {
int m;
while (~scanf("%d", &m)) {
memset(t, 0, sizeof(t));
while (m--) {
int op;
scanf("%d", &op);
if (op == 0) {
int x;
scanf("%d", &x);
update(x, 1);
} else if (op == 1) {
int x;
scanf("%d", &x);
if (getsum(x) - getsum(x - 1) == 0) //若x存在 则getsum(x)-getsum(x-1)至少为1
printf("No Elment!\n");
else
update(x, -1); //存在则删除一个
} else {
int a, k;
scanf("%d %d", &a, &k);
int x = getsum(a);
if (getsum(N) - x < k) // getsum(maxn)=当前序列元素个数
printf("Not Find!\n");
else {
k += x;
printf("%d\n", find_kth(k));
}
}
}
}
return 0;
}