HDU2852 KiKi‘s K-Number (线段树 树上二分 / 树外二分)

题目链接: 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;
}

END

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值