区间第k小(分块 + 二分)

Posted jpphy0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间第k小(分块 + 二分)相关的知识,希望对你有一定的参考价值。

问题

序列 a 1 、 ⋯ 、 a n a_1、\\cdots 、a_n a1an,对序列进行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 lognlogV)

分析

  • 基本思想:分块
  • 对分块排序
  • 对指定的区间使用二分法求解小于等于某个值的个数

代码

#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小(分块 + 二分)的主要内容,如果未能解决你的问题,请参考以下文章

第k小问题

区间第k大数

静态区间第k小 - 整体二分

HDU 6231 (二分+双指针)

LibreOJ 6278 数列分块入门 2(分块区间加法,二分)

分块的一道题