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