【分块】LibreOJ 6281 数列分块入门5
前言
对一个 int 类型的非负整数进行开方下取整,最多只会开方四次大小就不会再发生变化。一个大于 \(0\) 的正整数开方下取整最后的结果比如是 \(1\),而 \(1\) 开方的结果仍然会是 \(1\);\(0\) 开方的结果仍是 \(0\)。
验证int类型整数最多可以开方的次数的demo
#include<bits/stdc++.h>
using namespace std;
int main() {
int a = 2147483647;
for (int i = 0; i < 32; ++ i) {
cout << a << endl;
a = sqrt(a);
if (a == 1) {
cout << "i = " << i;
break;
}
}
return 0;
}
题目
题解
将 \(n\) 个元素的数组 \(a\) 按块长 \(\sqrt{n}\) 进行分块处理。为每个块设置开方懒标记 \(sq[i]\),代表这个区间每个元素共同开方的次数,并且用 \(flag[i]\) 标记 \(sq[i]\) 是否发生变动。同时为每个块设置 \(cnt[i]\) 和 \(sum[i]\),分别代表这个块上开方后大小会变化的元素个数和这个块的所有元素之和。而每个数最多只会开方 \(4\) 次,不妨直接用 \(a[i][j]\) 来维护第 \(i\) 个数开方 \(j\) 次后的值,同时用 \(b[i]\) 维护当前已经开方的次数(亦即 \(a[i][j]\) 中 \(j\) 的值)。
对于区间开方操作,对于整个块的元素都进行开方操作的块判断是否仍存在开方后会变小的元素(即 \(cnt[i] > 0\) 是否成立),若存在则将懒开方标记 \(sq[i] = sq[i] + 1\),并且设置 \(flag[i] = true\) 来标记 \(sq[i]\) 已发生变动,单次操作时间复杂度 \(O(1)\),此类块至多只有 \(\sqrt{n}\) 块,最差时间复杂度 \(O(\sqrt{n})\);未符合整块的进行开方操作则进行暴力处理,并且更新其属于的块的 \(sum[i]\),单次操作时间复杂度 \(O(\sqrt{n})\),此类块至多只有 \(2\) 块,最差时间复杂度 \(O(2\sqrt{n})\)。
对于区间和操作,对于整个块的元素都需要计算到结果中的块判断这个块区间开方懒标记 \(sq[i]\) 是否发生变动(即 \(flag[i] == false\) 是否成立),若成立说明区间开方懒标记 \(sq[i]\) 未发生变动,因此将覆盖到的整块的 \(sum[i]\) 直接添加到结果中即可,单次操作时间复杂度 \(O(1)\),此类块至多只有 \(\sqrt{n}\) 块,最差时间复杂度 \(O(\sqrt{n})\);若不成立,则需要重构这个块,重新计算 \(sum[i], cnt[i]\),并且重新将 \(flag[i]\) 重新置为 \(false\),单次操作时间复杂度 \(O(\sqrt{n})\),每个块最多只会重构 \(4\) 次,最多重构 \(\sqrt{n}\) 块,最差时间复杂度 \(O(4n)\)。未符合整块的进行区间和操作则进行暴力处理单次操作时间复杂度 \(O(\sqrt{n})\),此类块至多只有 \(2\) 块,最差时间复杂度 \(O(2\sqrt{n})\)。
参考代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
char op;//操作
int l, r, c;
int n;//数列长度
int len;//块长
int a[50005][6];//数列,每个数最多开方5次就不会再变小
int b[50005];//维护数列 a 的第二维的下标
int cnt[230];//标记一个区间上开方还会变小的数的个数
int sq[230];//区间开方次数懒标记
bool flag[230];//标记一个区间开方次数懒标记增加
ll sum[230];//区间和
/*获取下标 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;
}
/*获取第 x 块的左边界*/
int getLeftIdx(int x) {
return (x - 1) * len + 1;
}
/*获取第 x 块的右边界*/
int getRightIdx(int x) {
return min(x * len, n);
}
/*初始化块*/
void initPieces() {
len = sqrt(n);
for (int i = 1, j = 1; i <= n; ++ i) {
sum[j] += a[i][0];
if (a[i][0] > 1) ++ cnt[j];
if (a[i][0]) for (int k = 1; k < 6; ++ k) a[i][k] = sqrt(a[i][k - 1]);
if (isRightBoundary(i)) ++ j;
}
}
/* 计算第 x 块的区间和*/
ll calc(int x) {
if (!flag[x]) return sum[x];
cnt[x] = 0;//重新累计cnt[x]
flag[x] = false;
sum[x] = 0LL;//重新计算sum[x]
for (int i = getLeftIdx(x), j = getRightIdx(x); i <= j; ++ i) {
int bidx = min(5, b[i] + sq[x]);//维护二维的索引,最大只会到 5
sum[x] += a[i][bidx];
if (a[i][bidx] > 1) ++ cnt[x];
}
return sum[x];
}
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][0];
initPieces();
for (int p = 0; p < n; ++ p) {
cin >> op >> l >> r >> c;
int le = getPieceId(l), ri = getPieceId(r);
bool isLe = isLeftBoundary(l), isRi = isRightBoundary(r);
if (op == '0') {//区间开方
for (int i = isLe ? le : le + 1, j = isRi ? ri : ri - 1; i <= j; ++ i) {
if (cnt[i] > 0) {//区间上存在开方后会变小的数
sq[i] ++;
flag[i] = true;
}
}
if (!isLe) {
while (l <= r) {
int bidx1 = min(5, b[l] + sq[le]), bidx2 = min(5, bidx1 + 1);
sum[le] -= a[l][bidx1];
sum[le] += a[l][bidx2];
b[l] = b[l] + 1;
if (a[l][bidx1] > 1 && a[l][bidx2] <= 1) -- cnt[le];
if (isRightBoundary(l)) break;
++ l;
}
}
if (!isRi) {
while (l <= r) {
int bidx1 = min(5, b[r] + sq[ri]), bidx2 = min(5, bidx1 + 1);
sum[ri] -= a[r][bidx1];
sum[ri] += a[r][bidx2];
b[r] = b[r] + 1;
if (a[r][bidx1] > 1 && a[r][bidx2] <= 1) -- cnt[ri];
if (isLeftBoundary(r)) break;
-- r;
}
}
} else {//区间和
ll ans = 0LL;
for (int i = isLe ? le : le + 1, j = isRi ? ri : ri - 1; i <= j; ++ i) ans += calc(i);
if (!isLe) {
while (l <= r) {
ans += a[l][min(5, b[l] + sq[le])];
if (isRightBoundary(l)) break;
++ l;
}
}
if (!isRi) {
while (l <= r) {
ans += a[r][min(5, b[r] + sq[ri])];
if (isLeftBoundary(r)) break;
-- r;
}
}
cout << ans << '\n';
}
}
return 0;
}
浙公网安备 33010602011771号