【洛谷】P2801 教主的魔法
作者:互联网
题目地址:
https://www.luogu.com.cn/problem/P2801
题目描述:
教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是
N
N
N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为
1
,
2
,
…
,
N
1, 2, \ldots, N
1,2,…,N。每个人的身高一开始都是不超过
10001000
10001000
10001000的正整数。教主的魔法每次可以把闭区间
[
L
,
R
]
[L, R]
[L,R](
1
≤
L
≤
R
≤
N
1≤L≤R≤N
1≤L≤R≤N)内的英雄的身高全部加上一个整数
W
W
W。(虽然
L
=
R
L=R
L=R时并不符合区间的书写规范,但我们可以认为是单独增加第
L
(
R
)
L(R)
L(R)个英雄的身高)CYZ、光哥和ZJQ等人不信教主的邪,于是他们有时候会问WD闭区间
[
L
,
R
]
[L, R]
[L,R]内有多少英雄身高大于等于
C
C
C,以验证教主的魔法是否真的有效。WD巨懒,于是他把这个回答的任务交给了你。
输入格式:
第
1
1
1行为两个整数
N
,
Q
N, Q
N,Q。
Q
Q
Q为问题数与教主的施法数总和。
第
2
2
2行有
N
N
N个正整数,第
i
i
i个数代表第
i
i
i个英雄的身高。
第
3
3
3到第
Q
+
2
Q+2
Q+2行每行有一个操作:若第一个字母为
M
M
M,则紧接着有三个数字
L
,
R
,
W
L, R, W
L,R,W。表示对闭区间
[
L
,
R
]
[L, R]
[L,R]内所有英雄的身高加上
W
W
W。若第一个字母为
A
A
A,则紧接着有三个数字
L
,
R
,
C
L, R, C
L,R,C。询问闭区间
[
L
,
R
]
[L, R]
[L,R]内有多少英雄的身高大于等于
C
C
C。
输出格式:
对每个
A
A
A询问输出一行,仅含一个整数,表示闭区间
[
L
,
R
]
[L, R]
[L,R]内身高大于等于
C
C
C的英雄数。
数据范围:
对于
30
%
30\%
30%的数据,
N
≤
1000
N≤1000
N≤1000,
Q
≤
1000
Q≤1000
Q≤1000。对于
100
%
100\%
100%的数据,
N
≤
1
0
6
N≤10^6
N≤106,
Q
≤
3000
Q≤3000
Q≤3000,
1
≤
W
≤
1000
1≤W≤1000
1≤W≤1000,
1
≤
C
≤
1
0
9
1≤C≤10^9
1≤C≤109。
其实是要实现一个数据结构可以做两种操作,一个是区间增加某个数
x
x
x,另一个是查询区间里大于等于某个数的个数有多少个。可以用分块的思想来做。将
[
l
,
r
]
[l,r]
[l,r]里的数增加
v
v
v,这个操作可以用分块 + 懒标记来做,但是求
[
l
,
r
]
[l,r]
[l,r]里大于等于
v
v
v的数的个数,这个操作光靠分块是不行的,可以将每个块都存下来并保持有序,这样整块内就可以二分来做,在散块里的还需要暴力求解。综上,两个操作我们这样做:
1、初始化的时候,开个vector向量
v
v
v,
v
[
i
]
v[i]
v[i]存第
i
i
i个分块里的所有数,并排好序(
v
[
i
]
v[i]
v[i]本身是个vector);
2、区间
[
l
,
r
]
[l,r]
[l,r]增加
v
v
v的操作,如果区间属于一个整块,则直接暴力做;对于在散块里的数,也直接暴力做。这两种情况下做完了要将
v
[
i
]
v[i]
v[i]更新并排序;而对于整块里的数,只做懒标记;
3、区间
[
l
,
r
]
[l,r]
[l,r]里查询大于等于
v
v
v的数的个数,如果区间属于一个整块,或者对于在散块里的数,这两种情况都是暴力计数(注意要加懒标记);对于在整块里的数,由于整块里的数在
v
[
i
]
v[i]
v[i]里都排好序了,所以可以直接二分来计数(也要加懒标记)。
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int N = 1e6 + 10, M = 1050;
int n, q, len;
int w[N], lazy[M];
vector<int> BQ[M];
// 得到i是属于第几个分块
int get(int i) {
return i / len;
}
void init() {
for (int i = 0; i < n; i++) BQ[get(i)].push_back(w[i]);
for (int i = 0; i < n; i += len) sort(BQ[get(i)].begin(), BQ[get(i)].end());
}
// 更新第u个分块
void update(int u) {
vector<int> &v = BQ[u];
// 清空后把数重新加进去并排序
v.clear();
for (int i = u * len; i < min(n, (u + 1) * len); i++)
v.push_back(w[i]);
sort(v.begin(), v.end());
}
// 将区间[l, r]中每个数增加v
void add(int l, int r, int v) {
if (get(l) == get(r)) {
// 属于同一个整块,暴力做完后更新这个整块
for (int i = l; i <= r; i++) w[i] += v;
update(get(l));
} else {
// 先将散块暴力更新然后更新块的信息
int i = l, j = r;
while (get(i) == get(l)) w[i++] += v;
update(get(l));
while (get(j) == get(r)) w[j--] += v;
update(get(r));
// 对于整块只做懒标记即可
for (int k = get(i); k <= get(j); k++) lazy[k] += v;
}
}
// 查询[l, r]里大于等于v的数的个数
int query(int l, int r, int v) {
int res = 0;
if (get(l) == get(r)) {
// 如果在一个整块里,则直接暴力计数
for (int i = l; i <= r; i++)
if (w[i] + lazy[get(l)] >= v) res++;
} else {
// 否则先暴力计数在散块里的数
int i = l, j = r;
while (get(i) == get(l)) {
if (w[i] + lazy[get(i)] >= v) res++;
i++;
}
while (get(j) == get(r)) {
if (w[j] + lazy[get(j)] >= v) res++;
j--;
}
// 对于整块,由于整块已经都排好序了,所以可以直接二分求解
for (int k = get(i); k <= get(j); k++) {
vector<int> &vec = BQ[k];
int l = 0, r = vec.size() - 1;
while (l < r) {
int mid = l + (r - l >> 1);
if (vec[mid] + lazy[k] >= v) r = mid;
else l = mid + 1;
}
if (vec[l] + lazy[k] >= v) res += vec.size() - l;
}
}
return res;
}
int main() {
scanf("%d%d", &n, &q);
len = sqrt(n);
for (int i = 0; i < n; i++) scanf("%d", &w[i]);
init();
while (q--) {
char op[2];
int l, r, v;
scanf("%s%d%d%d", op, &l, &r, &v);
if (op[0] == 'A') printf("%d\n", query(l - 1, r - 1, v));
else add(l - 1, r - 1, v);
}
return 0;
}
初始化时间复杂度 O ( N log N ) = O ( N log N ) O(N {\log \sqrt N})=O(N\log N) O(NlogN )=O(NlogN),每次操作时间复杂度 O ( N log N ) = O ( N log N ) O(\sqrt N\log \sqrt N)=O(\sqrt N\log N) O(N logN )=O(N logN),空间 O ( N ) O(N) O(N)。
标签:洛谷,log,get,int,整块,P2801,魔法,++,1000 来源: https://blog.csdn.net/qq_46105170/article/details/118925878