分块【2023.2.1】

一.引入

没有引入就是最好的引入

——鲁迅 \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} sumrsuml1呗,就一道黄题怎么会是灰题呢?

(二)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 1n105(我们万恶的竞赛教练就是这么出的

你…倒在了电脑前,被一道小小的灰题打败了。

你不知道的是,一年后的莫涛队长(被尊称为莫队)发明了两种算法(莫队和分块,完美的解决了此类问题)

分块,顾名思义,就是把一段序列分成一小块一小块来处理,维护。

我们把一段当成一个整体,只记录维护整体的有关信息,就是分块。

首先,对于前言说得那道题,很朴素的做法就是:

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(lir);

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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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+=xblock(元素个数);

( 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. 其实这里的时间复杂度并不准确,只是大多数的代码近似于这个时间复杂度 ↩︎

  2. 洛谷(一个 O I OI OI网站中)题目的难度分为灰, 红, 橙, 黄, 绿, 蓝, 紫, 黑, 灰。难度依次递增,而灰题是未判定难度的题。 ↩︎

  3. 一种树形结构,可以维护区间求和和单点修改的优秀数据结构 ↩︎

  4. 一种更加优秀的数据结构,可以完成大量的操作 ↩︎

  5. 完整块:被操作区间完全覆盖的块 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  6. 不完整块:操作区间不完全覆盖的块 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  7. 一种能在有序列表内,用 O ( log ⁡ n ) \mathcal{O(\log n)} O(logn)时间复杂度找到第一个大于等于所查询值的___地址___,也是 C C C++的自带函数 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值