前言
分块是一种优雅的暴力,将数组按块长 \(\sqrt{n}\) 进行分块,可实现区间加法、区间求和和区间逆序对计数等场景,进行 \(m\) 次操作的时间复杂度:\(O(m\sqrt{n})\)。
对于整个块都进行操作,可以用打上标记的方式来取代操作这个块的全部元素,由于最多只需要处理 \(\sqrt{n}\) 个块,因此这个操作的时间复杂度是 \(O(\sqrt{n})\)。对于不属于整个块的部分,直接进行暴力处理,易知这样子的块最多只有两个,需要处理的元素至多只有 \(2 * \sqrt{n} - 2\) 个,因此这步操作时间复杂度也是 \(O(\sqrt{n})\)。
题目
题解
将 \(n\) 个元素的数组 \(a\) 按块长 \(\sqrt{n}\) 进行分块处理。为每个块设置一个懒添加标记 \(add[i]\),代表这个区间每个元素共同添加的数值大小。
对于 \(opt = 0\) 的情况:将添加值存储在符合整块都进行加法操作的块的懒标记 \(add[i]\) 上,未符合整块都进行加法操作则进行暴力处理。
对于 \(opt = 1\) 的情况:直接输出 \(a[r] + add[getPieceId(r)]\)。
参考代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int n;//数列元素个数
int op, l, r, c;
int len;//块长
ll a[50005];//数列
ll add[230];//每个块的懒添加标记
/*初始化块*/
void initPieces() {
len = sqrt(n);
}
/*获取下标 x 所在的块的索引*/
int getPieceId(int x) {
return (x - 1) / len + 1;
}
/*判断下标 x 是否为块的左边界*/
bool isLeftBoundary(int x) {
return (x - 1) % len == 0;
}
/*判断下标 x 是否为块的右边界*/
bool isRightBoundary(int x) {
return x % len == 0;
}
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; ++ i) cin >> a[i];
initPieces();
for (int i = 0; i < n; ++ i) {
cin >> op >> l >> r >> c;
if (op) {
cout << a[r] + add[getPieceId(r)] << '\n';
} else {
bool isLe = isLeftBoundary(l), isRi = isRightBoundary(r);
int le = getPieceId(l), ri = getPieceId(r);
//首先处理整块的内容
for (int i = isLe ? le : le + 1, j = isRi ? ri : ri - 1; i <= j; ++ i) add[i] += c;
//其次处理左边不满一块的内容
if (!isLe) {
while (l <= r) {
a[l] += c;
if (isRightBoundary(l)) break;
++ l;
}
}
//最后处理右边不满一块的内容
if (!isRi) {
while (l <= r) {
a[r] += c;
if (isLeftBoundary(r)) break;
-- r;
}
}
}
}
return 0;
}
标签:6277,LibreOJ,分块,int,len,add,sqrt,数列
From: https://www.cnblogs.com/RomanLin/p/18556509