区间第k小(分块 + 二分)
Posted jpphy0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间第k小(分块 + 二分)相关的知识,希望对你有一定的参考价值。
问题
序列 a 1 、 ⋯ 、 a n a_1、\\cdots 、a_n a1、⋯、an,对序列进行m次区间操作,可能是区间每个元素同时增加一个值,或则查询区间内第K小的值。时间复杂度要求,修改: O ( n ) O(\\sqrt{n}) O(n),查询: O ( n ⋅ log n ⋅ log V ) O( \\sqrt{n} \\cdot \\log {n} \\cdot \\log{V} ) O(n⋅logn⋅logV)
分析
- 基本思想:分块
- 对分块排序
- 对指定的区间使用二分法求解小于等于某个值的个数
代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 50010
#define MAXS 256
#define inf 1000000000
const int K = 8;
int st[MAXS], en[MAXS];
int n, m, a[MAXN]={inf}, b[MAXN], tmp[MAXS<<1], tag[MAXS];
// 二路归并排序:有序区间[start, end]的子区间[l,r]元素加t
void sort(int start, int l, int r, int end, int t){
int p1 = 0, p2 = MAXS;
for(int i = start; i <= end; ++i){
if(b[i] >= l && b[i] <= r) tmp[p2] = b[i], ++p2, a[b[i]] += t;
else tmp[p1] = b[i], ++p1;
}
tmp[p1] = 0, tmp[p2] = 0, p1 = 0, p2 = MAXS;
for(int i = start; i <= end; ++i){
if(a[tmp[p1]] < a[tmp[p2]]) b[i] = tmp[p1], ++p1;
else if(tmp[p2] != 0) b[i] = tmp[p2], ++p2;
}
}
// [l,r]区间内小于等于val的个数
int queryup(int p[], int l, int r, int val){
int res = 0;
res = upper_bound(p+l, p+r+1, val, [p](int v, int mid){
return a[mid] > v;
})- p;
if(res > r) res = r-l+1;
else if(a[p[res]] > val) res -= l;
else res = res - l + 1;
return res;
}
void query(int l, int r, int k){
int ans, vl = 0, vr = inf, vm;
int L = l >> K, R = r >> K;
int p1 = 0, p2 = MAXS;
if(L < R){
for(int i = st[L]; i <= en[L]; ++i) if(b[i] >= l) tmp[++p1] = b[i];
for(int i = st[R]; i <= en[R]; ++i) if(b[i] <= r) tmp[++p2] = b[i];
}
else
for(int i = st[L]; i <= en[L]; ++i)
if(b[i] >= l && b[i] <= r) tmp[++p1] = b[i];
while(vl < vr){
ans = 0;
vm = (vl+vr)/2;
if(L < R){
for(int i = L+1; i < R; ++i)
ans += queryup(b, st[i], en[i], vm-tag[i]);
ans += queryup(tmp, 1, p1, vm-tag[L]);
ans += queryup(tmp, MAXS+1, p2, vm-tag[R]);
}
else ans += queryup(tmp, 1, p1, vm-tag[L]);
if(ans < k) vl = vm+1;
else vr = vm;
}
printf("%d\\n", vl);
}
void update(int l, int r, int t){
int L = l >> K, R = r >> K;
if(L == R)
sort(st[L], l, r, en[L], t);
else{
sort(st[L], l, en[L], en[L], t);
sort(st[R], st[R], r, en[R], t);
for(int i = L+1; i < R; ++i) tag[i] += t;
}
}
int main(){
int T, op, l, r, kt;
scanf("%d", &T);
while(T--){
memset(tag, 0, sizeof tag);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++i) en[i>>K] = i;
for(int i = n; i >= 1; --i) st[i>>K] = i;
for(int i = 1; i <= n; ++i) scanf("%d", a+i);
for(int i = 1; i <= n; ++i) b[i] = i;
for(int i = 0; i <= n >> K; ++i){ // 各块排序
sort(b+st[i], b+en[i]+1, [](int x, int y){
return a[x] < a[y];
});
}
while(m--) {
scanf("%d%d%d%d", &op, &l, &r, &kt);
if(op == 1) update(l, r, kt);
else query(l, r, kt);
}
}
return 0;
}
以上是关于区间第k小(分块 + 二分)的主要内容,如果未能解决你的问题,请参考以下文章