【ybt金牌导航6-6-1】区间第k小 / 整体二分例题

区间第k小

题目链接:ybt金牌导航6-6-1

题目大意

对于一个序列,多次询问。
每次求一个区间的第 k 小值。

思路

这题是一道整体二分的例题。

二分大家都知道,但是整体二分又是什么呢?
没错,它就是把答案看做一个整体,直接二分,在二分的过程中算出答案。

那我们这里有那么多的询问,我们就把询问和插入(把序列当做插入一个数)放在一个序列中,然后 \(work(l,r,L,R)\) 表示序列 \(l\sim r\) 的答案(如果有答案)在 \(L\sim R\) 这段区间中。
没错,我们就是二分 \(L\sim R\),然后根据这个确定 \(l\sim r\)

那我们想,你二分答案是 \(mid\),然后枚举你范围中的操作。
如果是插入操作,你就看它插入的大小会不会对答案造成影响,如果它的大小在 \(mid\) 前面,就会影响,要记录下来(用什么记录后面会说)。然后把它放在左区间。否则就是放在右区间。

那如果是查询操作呢?你就要看它查询的区间中有多少个记录的数(也就是这个区间中 \(mid\) 的排名)。那这就要用到前缀和,还是要支持修改,自然会想到用树状数组,那就用树状数组来修改和查询。
那如果排名比这个询问询问的大了,就说明这个数要小一点,就把它放在左区间,否则就是放在右区间。

然后你就把两个区间并在一起,然后二分下去。

但是每次搞完你要记得清空树状数组,为了减少花费的时间,我们可以原路的把你之前加的减回去。

代码

#include<cstdio>
#define INF (1e9 + 7)

using namespace std;

struct node {
	int x, y, op, id, k;
}q[200001], q1[200001], q2[200001];
int n, m, x, y, z, tot;
int ans[100001];
int tree[100001];

void add(int x, int y) {//树状数组操作
	for (; x <= n; x += x & (-x))
		tree[x] += y;
}

int ask(int x) {
	int re = 0;
	for (; x; x -= x & (-x))
		re += tree[x];
	return re;
}

void work(int l, int r, int L, int R) {
	if (l > r) return ;//没有询问在这个答案区间
	if (L == R) {//已经确定答案的范围
		for (int i = l; i <= r; i++)
			if (q[i].op == 2) ans[q[i].id] = L;
		return ;
	}
	
	int mid = (L + R) >> 1;
	int t1 = 0, t2 = 0;
	for (int i = l; i <= r; i++) {
		if (q[i].op == 1) {//插入操作
			if (q[i].x <= mid) {//在左边的
				add(q[i].id, 1);
				q1[++t1] = q[i];
			}
			else {//在右边的
				q2[++t2] = q[i];
			}
		}
		else {
			int num = ask(q[i].y) - ask(q[i].x - 1);//统计比左边的区间多少个大
			if (num >= q[i].k) {//排名大了,应该放在左边
				q1[++t1] = q[i];
			}
			else {//排名小了,应该放在右边
				q[i].k -= num;
				q2[++t2] = q[i];
			}
		}
	}
	
	for (int i = 1; i <= t1; i++)//清空树状数组
		if (q1[i].op == 1) add(q1[i].id, -1);
	
	for (int i = 1; i <= t1; i++)//把分好左右区间的序列弄出来
		q[l + i - 1] = q1[i];
	for (int i = 1; i <= t2; i++)
		q[l + t1 + i - 1] = q2[i];
	
	work(l, l + t1 - 1, L, mid);//二分下去
	work(l + t1, r, mid + 1, R);
}

int main() {
	scanf("%d %d", &n, &m);
	
	for (int i = 1; i <= n; i++) {
		scanf("%d", &x);
		tot++;
		q[tot].x = x;
		q[tot].op = 1;
		q[tot].id = i;
	}
	
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &x, &y, &z);
		tot++;
		q[tot].x = x;
		q[tot].y = y;
		q[tot].op = 2;
		q[tot].id = i;
		q[tot].k = z;
	}
	
	work(1, tot, -INF, INF);
	
	for (int i = 1; i <= m; i++)
		printf("%d\n", ans[i]);
	
	return 0;
}
posted @ 2021-02-25 14:24  あおいSakura  阅读(47)  评论(0编辑  收藏  举报