题目链接: KiKi’s K-Number
大致题意
有一个空的容器, 有三种操作:
0 a
把a加入容器中
1 a
把a从容器中删除. 若有多个, 只删除一个, 若不存在a, 则输出 “No Elment!”
2 a k
求整个容器中, 第k个比a大的数.
特别注意, 第三个操作是求第k个比a大的数字, 而不是第k大数
解题思路
权值线段树 树状数组的normal题目当然要用线段树来写啦!
我们直接分析操作, 相当于是一个单点修改 和 一个需要分析的查询操作.
那么我们如何去寻找第k个比a大的数字?
方法一: 树外二分 + 区间查询
我们可以用二分的方式来假设当前mid为答案, 我们看看当前mid所在的位置是不是a后的第k个数字.
具体做法为: 我们先得到a当前处于第pos位置, 相当于我们要找一个数, 出现在pos + k位置.
这样单次查询的复杂度为: O(lognlogn)
方法二: 树上二分 + 区间查询
我们通过方法一, 发现我们是想找一个静态区间的第k小数, 且这个区间不是[l, r]的形式, 而是[1, pos]的形式.
权值线段树本身是可以实现形如[1, pos]形式的第k小树询问的, 并不需要主席树
我们每次看当前区间左子树有多少个数字, 如果大于等于k, 则递归左子树, 反之减去左子树的贡献, 递归右子树.
这样我们就实现了树上二分. 单次查询复杂度为: O(logn).
AC代码
/* 树上二分 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, INF = 0x3f3f3f3f;
struct node {
int l, r;
int cou;
}t[N << 2];
void pushup(int x) { t[x].cou = t[x << 1].cou + t[x << 1 | 1].cou; }
void build(int l, int r, int x = 1) {
t[x] = { l, r, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
bool modify(int a, int c, int x = 1) {
if (t[x].l == t[x].r) {
t[x].cou += c;
if (t[x].cou < 0) {
t[x].cou = 0;
return 0;
}
return 1;
}
int mid = t[x].l + t[x].r >> 1;
bool res = modify(a, c, x << 1 | (a > mid));
pushup(x);
return res;
}
int getnum(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x].cou;
int mid = t[x].l + t[x].r >> 1;
int res = 0;
if (l <= mid) res += getnum(l, r, x << 1);
if (r > mid) res += getnum(l, r, x << 1 | 1);
return res;
}
int ask(int k, int x = 1) {
if (t[x].l == t[x].r) return t[x].cou ? t[x].l : INF;
if (k > t[x << 1].cou) return ask(k - t[x << 1].cou, x << 1 | 1);
return ask(k, x << 1);
}
int main()
{
const int len = N - 5;
int n;
while (~scanf("%d", &n)) {
build(1, len);
rep(i, n) {
int tp; scanf("%d", &tp);
if (!tp) {
int c; scanf("%d", &c);
modify(c, 1);
}
else if (tp == 1) {
int c; scanf("%d", &c);
if (!modify(c, -1)) puts("No Elment!");
}
else {
int a, k; scanf("%d %d", &a, &k);
k += getnum(1, a);
int res = ask(k);
if (res == INF) puts("Not Find!");
else printf("%d\n", res);
}
}
}
return 0;
}
/* 树外二分 */
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, INF = 0x3f3f3f3f;
struct node {
int l, r;
int cou;
}t[N << 2];
void pushup(int x) { t[x].cou = t[x << 1].cou + t[x << 1 | 1].cou; }
void build(int l, int r, int x = 1) {
t[x] = { l, r, 0 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
bool modify(int a, int c, int x = 1) {
if (t[x].l == t[x].r) {
t[x].cou += c;
if (t[x].cou < 0) {
t[x].cou = 0;
return 0;
}
return 1;
}
int mid = t[x].l + t[x].r >> 1;
bool res = modify(a, c, x << 1 | (a > mid));
pushup(x);
return res;
}
int getnum(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x].cou;
int mid = t[x].l + t[x].r >> 1;
int res = 0;
if (l <= mid) res += getnum(l, r, x << 1);
if (r > mid) res += getnum(l, r, x << 1 | 1);
return res;
}
int main()
{
const int len = N - 5;
int n;
while (~scanf("%d", &n)) {
build(1, len);
rep(i, n) {
int tp; scanf("%d", &tp);
if (!tp) {
int c; scanf("%d", &c);
modify(c, 1);
}
else if (tp == 1) {
int c; scanf("%d", &c);
if (!modify(c, -1)) puts("No Elment!");
}
else {
int a, k; scanf("%d %d", &a, &k);
k += getnum(1, a);
int l = a + 1, r = len;
while (l < r) {
int mid = l + r >> 1;
if (getnum(1, mid) >= k) r = mid;
else l = mid + 1;
}
if (l == len) puts("Not Find!");
else printf("%d\n", l);
}
}
}
return 0;
}