【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;
}