【ybt金牌导航6-3-2】区间计数(分块)(二分)

区间计数

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

题目大意

给你一个数组,要你支持几个操作:
区间加值和查询一个区间有多少个大于等于某个值的数。

思路

我们考虑用分块来做。
区间加值很容易,暴力加和整块打标记就行。
那接着问题是查询,维护区间有多少大于等于某个值的数看起来不现实。
那我们考虑搞点东西,让它可以比较好的求。不难想到可以将区间内数排序,然后询问就二分。
那你区间修改的时候整体修改不用重新排序,单独暴力修改的才要排序。
那修改和询问都只是加了个 \(log\sqrt{n}\),复杂度仍然可以接受。
那就可以了。

代码

#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

int n, q, a[1000002], sa[1000002];
int S, s, bl[1002], br[1002];
int lz[1002], x, y, z;
char c;

int read() {
	int re = 0, zf = 1;
	c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') zf = -zf;
		c = getchar();
	}
	while (c >= '0' && c <= '9') {
		re = (re << 3) + (re << 1) + c - '0';
		c = getchar();
	}
	return re * zf;
}

void blocksort(int now) {//给这个块排序
	for (int i = bl[now]; i <= br[now]; i++)
		sa[i] = a[i];
	sort(sa + bl[now], sa + br[now] + 1); 
}

void premake() {//一开始搞好每个块
	for (int i = 1; i <= n; i++)
		if (i % S == 1) {
			br[s] = i - 1;
			bl[++s] = i;
		}
	br[s] = n;
	bl[s + 1] = br[s + 1] = n + 1;
	for (int i = 1; i <= s; i++)
		blocksort(i);
}

void add_val(int l, int r, int val) {
	int L = (l - 1) / S + 1, R = (r - 1) / S + 1;
	if (r - l + 1 <= 2 * S) {//直接暴力处理
		for (int i = l; i <= r; i++) a[i] += val;
		for (int i = L; i <= R; i++) blocksort(i);
		return ;
	}
	bool loneyl = 1, loneyr = 1;
	if (l == bl[L]) loneyl = 0, L--;//可能没有需要暴力处理的的
	if (r == br[R]) loneyr = 0, R++;
	for (int i = L + 1; i < R; i++)//整块的
		lz[i] += val;
	for (int i = l; i <= br[L]; i++)//两边暴力
		a[i] += val;
	if (loneyl) blocksort(L);
	for (int i = bl[R]; i <= r; i++)
		a[i] += val;
	if (loneyr) blocksort(R);
}

int find(int b, int lim) {//二分找第一个大于等于的位置(然后得到大于等于的个数)
	int l = bl[b], r = br[b], ans = br[b] + 1;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (sa[mid] >= lim) {
			ans = mid;
			r = mid - 1;
		}
		else l = mid + 1;
	}
	return br[b] - ans + 1;
}

int query(int l, int r, int lim) {
	int L = (l - 1) / S + 1, R = (r - 1) / S + 1, re = 0;
	if (r - l + 1 <= 2 * S) {//暴力处理
		for (int i = l; i <= r; i++)
			if (a[i] + lz[(i - 1) / S + 1] >= lim)
				re++;
		return re;
	}
	if (l == bl[L]) L--; if (r == br[R]) R++;
	for (int i = L + 1; i < R; i++) re += find(i, lim - lz[i]);//整块
	for (int i = l; i <= br[L]; i++)//两边
		if (a[i] + lz[L] >= lim) re++;
	for (int i = bl[R]; i <= r; i++)
		if (a[i] + lz[R] >= lim) re++;
	return re; 
}

int main() {
	n = read(); q = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	
	S = sqrt(n);
	premake();
	
	while (q--) {
		c = getchar();
		while (c != 'M' && c != 'A') c = getchar();
		if (c == 'M') {
			x = read(); y = read(); z = read();
			add_val(x, y, z);
			continue;
		}
		if (c == 'A') {
			x = read(); y = read(); z = read();
			printf("%d\n", query(x, y, z));
			continue;
		}
	}
	
	return 0;
}

posted @ 2021-07-08 11:47  あおいSakura  阅读(45)  评论(0编辑  收藏  举报