文章目录
一.引入
没有引入就是最好的引入
——鲁迅 \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad ——鲁迅 ——鲁迅
为了骗分,
O
I
e
r
OIer
OIer们研发出了
O
(
n
n
)
\mathcal{O(n \sqrt n)}
O(nn)的算法——分块1(我不会说是我想不出来引入
二.算法介绍
分块 —— —— ——优雅的暴力
—— c q b z l h y \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad \qquad ——cqbzlhy ——cqbzlhy
三.算法实现
在2009年的你面前有一道灰题2:
有一段连续的序列 a 1 a_1 a1 ~ a n a_n an,然后现在我们需要执行几类操作:
(一)1 l r
:求出
[
l
,
r
]
[l, \;r]
[l,r] 区间的和
你心里大喜,一看就是前缀和,记录 ∑ k = 1 i a k \sum_{k=1}^{i}{a_k} ∑k=1iak为 s u m i sum_i sumi,然后显然有 s u m i + 1 = s u m i + a i + 1 sum_{i+1}=sum_i+a_{i+1} sumi+1=sumi+ai+1,我们要求 ∑ i = l r a i \sum_{i=l}^{r}{a_i} ∑i=lrai就直接输出 s u m r − s u m l − 1 sum_r - sum_{l-1} sumr−suml−1呗,就一道黄题怎么会是灰题呢?
(二)2 l r x
:将
[
l
,
r
]
[l, \; r]
[l,r]区间加上
x
x
x
你微微一愣,但是你会一种叫线段树3的数据结构 ! ! 1 !!1 !!1, 完全可以水掉这道题.
(三)3 l r x
:在
[
l
,
r
]
[l, \; r]
[l,r]这个区间中,查询小于
x
x
x的前驱(比其小的最大元素)。
你暗暗吃惊,然后发现平衡树4可以水掉这道题.
数据范围:空间限制:
4
M
B
4MB
4MB, 时间限制:1500 ms, 对于
100
%
100\%
100%的数据,
1
≤
n
≤
1
0
5
1 \leq n \leq 10^5
1≤n≤105(我们万恶的竞赛教练就是这么出的
你…倒在了电脑前,被一道小小的灰题打败了。
你不知道的是,一年后的莫涛队长(被尊称为莫队)发明了两种算法(莫队和分块,完美的解决了此类问题)
分块,顾名思义,就是把一段序列分成一小块一小块来处理,维护。
我们把一段当成一个整体,只记录维护整体的有关信息,就是分块。
首先,对于前言说得那道题,很朴素的做法就是:
1.从询问区间的l到r扫过去,每回加上扫到的值,即 a n s = ∑ i = l r a i ans=\sum_{i=l}^{r}{a_i} ans=∑i=lrai
2.直接把 a i a_i ai重新赋值不就得了 a i + = x ( l ≤ i ≤ r ) a_i += x(l \leq i \leq r) ai+=x(l≤i≤r);
3.从询问区间的 l l l到 r r r扫过去,每回遇到 < x <x <x的位置,答案记录最大值
代码:
while(q --){
int opt, l, r, x;
read(opt), read(l), read(r);
if(opt == 1){
int sum = 0;
for(int i = l; i <= r; i ++)sum += a[i];
write(sum);
putchar('\n');
}
else if(opt == 2)for(int i = l; i <= r; i ++)a[i] += x;
else{
int res = 0;
for(int i = l; i <= r; i ++)
if(a[i] < k)res = max(a[i], res);
write(res);
putchar('\n');
}
}
没错,这种做法很傻对不对?(我可能是真的傻子,这代码都写
但是,分块就是在这个基础上暴力优化的!!!
假设我们总共的序列长度为
n
n
n,然后我们把它切成
n
\sqrt n
n块,然后把每一块里的东西当成一个整体来看
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u m sum sum 数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
3.如何求 x x x的前驱:
( 1 ) (1) (1)要求一个数的前驱,显然我们要求块内元素是有序的,这样就能用 l o w e r _ b o u n d lower\_bound lower_bound7对块内查询.
( 2 ) (2) (2)不完整的块暴力就好, 这样的话需要提前对每块里面的元素做一遍排序就好, 但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?
然后,这道题就用了一种看似高大上的方法做完了……比之前傻傻的暴力是不是好看很多呢
代码是例题三的哦~~:
四.例题
1.数列分块入门 1
思路:
1.如何得出答案:
输出 a i + l a z y a_i +lazy ai+lazy标记
2.如何区间修改:
**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
代码:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5;
#define bl belong[ql]
#define br belong[qr]
int n, block, len, a[MAXN], lazy[MAXN], belong[MAXN], l[MAXN], r[MAXN];
template<typename T>
void read(T &x){
x = 0;
T f = 1;
char ch = getchar();
while(ch > '9' || ch < '0'){
if(ch == '-')f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void init(){
block = sqrt(n);
len = n / block + (n % block != 0);
for(int i = 1; i <= n; i ++)belong[i] = (i - 1) / block + 1;
for(int i = 1; i <= len; i ++)l[i] = (i - 1) * block + 1, r[i] = i * block;
r[len] = n;
}
void update(int ql, int qr, int x){
if(bl == br){
for(int i = ql; i <= qr; i ++)a[i] += x;
return ;
}
for(int i = bl + 1; i < br; i ++)lazy[i] += x;
for(int i = ql; i <= r[bl]; i ++)a[i] += x;
for(int i = l[br]; i <= qr; i ++)a[i] += x;
}
int query(int x){
return a[x] + lazy[belong[x]];
}
int main(){
read(n);
for(int i = 1; i <= n; i ++)read(a[i]);
init();
for(int i = 1; i <= n; i ++){
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if(opt == 0)update(l, r, x);
else {
write(query(r));
putchar('\n');
}
}
return 0;
}
2.数列分块入门 2
思路
1.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
2.如何求 [ l , r ] [l, \; r] [l,r]区间内小于 x 2 x^2 x2的个数:
( 1 ) (1) (1)要求小于 x 2 x^2 x2的个数,显然我们要求块内元素是有序的,这样就能用 l o w e r _ b o u n d lower\_bound lower_bound7对块内查询,
输出 l o w e r _ b o u n d lower\_bound lower_bound传回的地址减去 l i l_i li(当前块的左端点)
( 2 ) (2) (2)不完整的块暴力就好, 这样的话需要提前对每块里面的元素做一遍排序就好, 但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5;
#define bl belong[ql]
#define br belong[qr]
int n, block, len, a[MAXN], lazy[MAXN], belong[MAXN], l[MAXN], r[MAXN], b[MAXN];
template<typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void init() {
block = sqrt(n);
len = n / block + (n % block != 0);
for (int i = 1; i <= n; i ++) {
belong[i] = (i - 1) / block + 1;
b[i] = a[i];
}
for (int i = 1; i <= len; i ++) {
l[i] = (i - 1) * block + 1, r[i] = min(i * block, n);
sort(b + l[i], b + r[i] + 1);
}
}
void update(int ql, int qr, int x) {
if (bl == br) {
for (int i = ql; i <= qr; i ++) a[i] += x;
for (int i = l[bl]; i <= r[br]; i ++) b[i] = a[i];
sort(b + l[bl], b + r[br] + 1);
return ;
}
for (int i = bl + 1; i < br; i ++) lazy[i] += x;
for (int i = ql; i <= r[bl]; i ++) a[i] += x;
for (int i = l[bl]; i <= r[bl]; i ++)b[i] = a[i];
sort(b + l[bl], b + r[bl] + 1);
for (int i = l[br]; i <= qr; i ++) a[i] += x;
for (int i = l[br]; i <= r[br]; i ++) b[i] = a[i];
sort(b + l[br], b + r[br] + 1);
}
int query(int ql, int qr, int x) {
int res = 0;
if (bl == br) {
for (int i = ql; i <= qr; i ++) res += (x > (a[i] + lazy[bl]));
return res;
}
for (int i = bl + 1; i < br; i ++) res += lower_bound(b + l[i], b + r[i] + 1, x - lazy[i]) - b - l[i];
for (int i = ql; i <= r[bl]; i ++) res += (x > (a[i] + lazy[bl]));
for (int i = l[br]; i <= qr; i ++) res += (x > (a[i] + lazy[br]));
return res;
}
int main() {
read(n);
for (int i = 1; i <= n; i ++) read(a[i]);
init();
for (int i = 1; i <= n; i ++) {
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if (opt == 0) update(l, r, x);
else {
write(query(l, r, x * x));
putchar('\n');
}
}
return 0;
}
3.数列分块入门 3
思路
在算法介绍中
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
#define bl belong[ql]
#define br belong[qr]
int n, block, len, a[MAXN], lazy[MAXN], belong[MAXN], l[MAXN], r[MAXN], b[MAXN];
template<typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void init() {
block = sqrt(n);
len = n / block + (n % block != 0);
for (int i = 1; i <= n; i ++) {
belong[i] = (i - 1) / block + 1;
b[i] = a[i];
}
for (int i = 1; i <= len; i ++) {
l[i] = (i - 1) * block + 1, r[i] = min(i * block, n);
sort(b + l[i], b + r[i] + 1);
}
}
void update(int ql, int qr, int x) {
if (bl == br) {
for (int i = ql; i <= qr; i ++) a[i] += x;
for (int i = l[bl]; i <= r[br]; i ++) b[i] = a[i];
sort(b + l[bl], b + r[br] + 1);
return ;
}
for (int i = bl + 1; i < br; i ++) lazy[i] += x;
for (int i = ql; i <= r[bl]; i ++) a[i] += x;
for (int i = l[bl]; i <= r[bl]; i ++) b[i] = a[i];
sort(b + l[bl], b + r[bl] + 1);
for (int i = l[br]; i <= qr; i ++) a[i] += x;
for (int i = l[br]; i <= r[br]; i ++) b[i] = a[i];
sort(b + l[br], b + r[br] + 1);
}
int query(int ql, int qr, int x) {
int res = -1;
if (bl == br) {
for (int i = ql; i <= qr; i ++)
if (a[i] + lazy[bl] < x && a[i] + lazy[bl] > res) res = a[i] + lazy[bl];
return res;
}
for (int i = bl + 1; i < br; i ++) {
int t = lower_bound(b + l[i], b + r[i] + 1, x - lazy[i]) - (b + l[i]);
if(!t)continue;
int tot = t + l[i] - 1;
if(b[tot] + lazy[i] < x && b[tot] + lazy[i] > res) res = b[tot] + lazy[i];
}
for (int i = ql; i <= r[bl]; i ++) if (a[i] + lazy[bl] < x && a[i] + lazy[bl] > res) res = a[i] + lazy[bl];
for (int i = l[br]; i <= qr; i ++) if (a[i] + lazy[br] < x && a[i] + lazy[br] > res) res = a[i] + lazy[br];
return res;
}
int main() {
read(n);
for (int i = 1; i <= n; i ++) read(a[i]);
init();
for (int i = 1; i <= n; i ++) {
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if (opt == 0) update(l, r, x);
else {
write(query(l, r, x));
putchar('\n');
}
}
return 0;
}
4.数列分块入门 4
思路
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u m sum sum 数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5;
#define bl belong[ql]
#define br belong[qr]
typedef long long ll;
int n, block, len, belong[MAXN], l[MAXN], r[MAXN];
ll lazy[MAXN], sum[MAXN], a[MAXN];
template<typename T>
void read(T &x){
x = 0;
T f = 1;
char ch = getchar();
while(ch > '9' || ch < '0'){
if(ch == '-')f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9'){
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x){
if(x < 0){
putchar('-');
x = -x;
}
if(x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void init(){
block = sqrt(n);
len = n / block + (n % block != 0);
for(int i = 1; i <= len; i ++){
l[i] = (i - 1) * block + 1, r[i] = min(i * block, n);
for(int j = l[i]; j <= r[i]; j ++)belong[j] = i, sum[i] += a[j];
}
}
void update(int ql, int qr, int x){
if(bl == br){
for(int i = ql; i <= qr; i ++)a[i] += x, sum[bl] += x;
return ;
}
for(int i = bl + 1; i < br; i ++)lazy[i] += x;
for(int i = ql; i <= r[bl]; i ++)a[i] += x, sum[bl] += x;
for(int i = l[br]; i <= qr; i ++)a[i] += x, sum[br] += x;
}
ll query(int ql, int qr, int mod){
ll res = 0;
if(bl == br){
for(int i = ql; i <= qr; i ++)res = (res + a[i] + lazy[bl] + mod) % mod;
return res;
}
for(int i = bl + 1; i < br; i ++)res = (res + sum[i] + lazy[i] * block + mod) % mod;
for(int i = ql; i <= r[bl]; i ++)res = (res + a[i] + lazy[bl] + mod) % mod;
for(int i = l[br]; i <= qr; i ++)res = (res + a[i] + lazy[br] + mod) % mod;
return res;
}
int main(){
read(n);
for(int i = 1; i <= n; i ++)read(a[i]);
init();
for(int i = 1; i <= n; i ++){
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if(opt == 0)update(l, r, x);
else {
write(query(l, r, x + 1));
putchar('\n');
}
}
return 0;
}
5.数列分块入门 5
思路
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u m sum sum 数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,直接暴力更新, 把 l a z y lazy lazy标记重新更新
( 2 ) (2) (2)对于不完整块,还是直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记重新更新。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5;
#define bl belong[ql]
#define br belong[qr]
typedef long long ll;
int n, block, len, belong[MAXN], l[MAXN], r[MAXN];
ll lazy[MAXN], sum[MAXN], a[MAXN];
template<typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void lazydown(int x) {
sum[x] = lazy[x] = 0;
for (int i = l[x]; i <= r[x]; i ++) sum[x] += a[i];
for (int i = l[x]; i <= r[x]; i ++)
if (a[i] > 1) {
lazy[x] = 1;
return ;
}
}
void init() {
block = sqrt(n);
len = n / block + (n % block != 0);
for (int i = 1; i <= len; i ++) {
l[i] = (i - 1) * block + 1, r[i] = min(i * block, n);
for (int j = l[i]; j <= r[i]; j ++) {
belong[j] = i, sum[i] += a[j];
if (a[j] > 1) lazy[i] = 1;
}
}
}
void update(int ql, int qr) {
if (bl == br) {
if (!lazy[bl]) return ;
for (int i = ql; i <= qr; i ++) a[i] = sqrt(a[i]);
lazydown(bl);
return ;
}
for (int i = bl + 1; i < br; i ++) {
if (!lazy[i]) continue;
for (int j = l[i]; j <= r[i]; j ++) a[j] = sqrt(a[j]);
lazydown(i);
}
for (int i = ql; i <= r[bl]; i ++) a[i] = sqrt(a[i]);
lazydown(bl);
for (int i = l[br]; i <= qr; i ++) a[i] = sqrt(a[i]);
lazydown(br);
}
ll query(int ql, int qr) {
ll res = 0;
if (bl == br) {
for (int i = ql; i <= qr; i ++) res += a[i];
return res;
}
for (int i = bl + 1; i < br; i ++) res += sum[i];
for (int i = ql; i <= r[bl]; i ++) res += a[i];
for (int i = l[br]; i <= qr; i ++) res += a[i];
return res;
}
int main() {
read(n);
for (int i = 1; i <= n; i ++)read(a[i]);
init();
for (int i = 1; i <= n; i ++) {
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if (opt == 0)update(l, r);
else {
write(query(l, r));
putchar('\n');
}
}
return 0;
}
6.数列分块入门 6
思路
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u m sum sum 数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
3.如何求 x x x的前驱:
( 1 ) (1) (1)要求一个数的前驱,显然我们要求块内元素是有序的,这样就能用 l o w e r _ b o u n d lower\_bound lower_bound7对块内查询.
( 2 ) (2) (2)不完整的块暴力就好, 这样的话需要提前对每块里面的元素做一遍排序就好, 但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
#define bl belong[ql]
#define br belong[qr]
#define pi pair<int, int>
#define fi first
#define se second
int n, block, len, a[MAXN << 1], m;
vector<int> b[MAXN];
template<typename T>
void read(T &x) {
x = 0;
T f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-') f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
x *= f;
}
template<typename T>
void write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x >= 10)write(x / 10);
putchar(x % 10 + 48);
}
void init() {
block = sqrt(n);
len = n / block + (n % block != 0);
for (int i = 1; i <= len; i ++) {
int l = (i - 1) * block + 1, r = min(block * i, n);
for (int j = l; j <= r; j ++) b[i].push_back(a[j]);
}
}
void build() {
n = 0;
for (int i = 1; i <= len; i ++) {
int l = b[i].size();
for (int j = 0; j < l; j ++) {
a[++ n] = b[i][j];
}
b[i].clear();
}
init();
}
pi query(int x){
int cnt = 1;
while (b[cnt].size() < x) x -= b[cnt ++].size();
return make_pair(cnt, x - 1);
}
void update(int pos, int x) {
pi p = query(pos);
int tot = p.fi, cnt = p.se;
b[tot].insert(b[tot].begin() + cnt, x);
if (b[tot].size() > block * 20) build();
}
int main() {
read(n), m = n;
for (int i = 1; i <= n; i ++) read(a[i]);
init();
for (int i = 1; i <= m; i ++) {
int opt, l, r, x;
read(opt), read(l), read(r), read(x);
if (opt == 0) {
update(l, r);
}
else {
pi t = query(r);
write(b[t.fi][t.se]);
putchar('\n');
}
}
return 0;
}
7.
思路
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u m sum sum 数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
3.如何求 x x x的前驱:
( 1 ) (1) (1)要求一个数的前驱,显然我们要求块内元素是有序的,这样就能用 l o w e r _ b o u n d lower\_bound lower_bound7对块内查询.
( 2 ) (2) (2)不完整的块暴力就好, 这样的话需要提前对每块里面的元素做一遍排序就好, 但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?
8.
思路
1.如何得出答案:
( 1 ) (1) (1)对于完整的块5,每个块需要有 s u n sun sun数组要维护这个块的所有元素的和。
( 2 ) (2) (2)对于不完整块6,因为元素比较少(最多有 n n n / 块数 = n \sqrt n n个) 即使是极限数据: n = 100000 n=100000 n=100000的时候也最多有 317 317 317个而外面是 O ( n ) \mathcal{O(n)} O(n), 所以是不会超的。综上,我们可以直接暴力扫这个小块统计答案,
2.如何区间修改:
这里,我们换种思路,**记录一个 l a z y lazy lazy**标记(由线段树启发得到),表示整个块被加上过多少了,
( 1 ) (1) (1)对于完整块,因为每个元素都被加上了 x x x, 我们直接 l a z y + = x lazy+=x lazy+=x,块内的和 a n s + = x ∗ b l o c k ans+=x*block ans+=x∗block(元素个数);
( 2 ) (2) (2)对于不完整块,直接暴力修改就好了,顺便可以把 l a z y lazy lazy标记清 0 0 0。
3.如何求 x x x的前驱:
( 1 ) (1) (1)要求一个数的前驱,显然我们要求块内元素是有序的,这样就能用 l o w e r _ b o u n d lower\_bound lower_bound7对块内查询.
( 2 ) (2) (2)不完整的块暴力就好, 这样的话需要提前对每块里面的元素做一遍排序就好, 但是当有修改的话,因为整个块同时加上(减去)一个数,每个数的相对大小是不会变的,但是如果是不完全块就会改变,这样的话,还是因为元素个数小,重新新排一下不就得了?