分块
分块的算法网上讲解有很多,这里粗略讲讲
分块就是一种优雅的暴力方法
例如我们需要进行区间求和,区间修改的操作,假如我们使用暴力的方法,对于每一次修改每一次求和都枚举这个区间进行相关操作,时间复杂度是 O ( n 2 ) O(n^2) O(n2),对于一些数据量 > 1 0 4 >10^4 >104的问题稳定超时。
分块大致上是将一个长度为n
的区间分成
n
\sqrt n
n块,然后对于n
次对区间的操作l,r
可以转换成
对块1
左边一格的单点操作以及对块1 块2
区间操作加上对块2
右边两格子的单点操作,同样时枚举进行暴力修改,对于一整块的我们对这个块打上标记,两边的我们暴力修改。当然对块的大小是仁者见仁,智者见智,当把块的大小设置为
n
\sqrt n
n时不能通过,把块开小点可能会有意想不到的ac。
如此以来分块的时间复杂度为 O ( n ∗ n ) O(n*\sqrt n) O(n∗n)
以下是LOJ
例题
传送门
LOJ #6277. 数列分块入门 1
题意
区间加法,单点查值
题解
belong[]
数组预处理出每一个节点所属的块的编号,lazy[]
数组为当前块所共同添加的数的大小,那么对与某个点r
的查询,查询结果即为
a
[
r
]
+
l
a
z
y
[
b
e
l
o
n
g
[
r
]
]
a[r]+lazy[belong[r]]
a[r]+lazy[belong[r]]
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
using namespace std;
const int N = 5e4 + 500;
int n, m, a[N], lazy[N], size, belong[N];
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
#endif
ios::sync_with_stdio(false);
// cout << N << endl;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int size = sqrt(n);
for (int i = 1; i <= n; i++) belong[i] = (i + size - 1) / size;
for (int opt, l, r, c, i = 1; i <= n; i++) {
cin >> opt >> l >> r >> c;
if (opt == 0) {
for (int i = belong[l]; i <= belong[r]; i++) lazy[i] += c;
for (int i = (belong[l] - 1) * size + 1; i < l; i++) a[i] -= c;
for (int i = r + 1; i <= belong[r] * size; i++) a[i] -= c;
} else if (opt == 1) {
// cout << r << ":" << endl;
cout << a[r] + lazy[belong[r]] << endl;
}
}
}
LOJ #6278. 数列分块入门 2
题意
区间加法,询问区间内小于某个值的元素个数。
题解
同样预处理出每一个点的所属的块的编号,对于每一个块都预处理进行排序。如此,当我们枚举某个块的小于某个值的元素个数时可以用lower_bound
函数进行较快的查询,本题需要注意的是由于对区间有加操作,所以对于两边的操作我们暴力修改完时应对两边所属的块也进行排序。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 5e4 + 5000;
int n, m, T, belong[N], siz;
long long a[N], b[N], lazy[N];
void maintain(int k) {
int l = (k - 1) * siz + 1;
for (int i = l; i < l + siz; i++) a[i] = b[i];
sort(a + l, a + l + siz);
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
siz = sqrt(n);
for (int i = 1; i <= n; i++) belong[i] = (i + siz - 1) / siz;
for (int i = n + 1; i <= (n + siz - 1) / siz * siz; i++) a[i] = INT_MAX;
for (int i = 1; i <= n; i += siz) {
sort(a + i, a + i + siz);
}
// cout << siz << endl;
// for (int i = 1; i <= n; i++)
// cout << a[i] << " " << belong[i] << endl;
for (int opt, l, r, c, i = 1; i <= n; i++) {
cin >> opt >> l >> r >> c;
if (opt == 0) {
for (int i = l; i <= min(belong[l] * siz, r); i++) b[i] += c;
maintain(belong[l]);
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) b[i] += c;
maintain(belong[r]);
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) lazy[i] += c;
} else {
long long tmp = 1ll * c * c;
int ans = 0;
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
int l = (i - 1) * siz + 1;
int k = lower_bound(a + l, a + l + siz, tmp - lazy[i]) - a;
ans += k - l;
}
for (int i = l; i <= min(belong[l] * siz, r); i++) ans += (b[i] + lazy[belong[i]] < tmp);
if (belong[l] != belong[r])
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) ans += (b[i] + lazy[belong[i]] < tmp);
cout << ans << endl;
}
}
}
LOJ #6279. 数列分块入门 3
题意
操作涉及区间加法,询问区间内小于某个值的前驱(比其小的最大元素)。
题解
对于每一个块我们用mutiset
可重集进行存储,这时查找前驱就极为方便,依然是用暴力的方法进行修改查询。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5 + 5000;
int n, m, T, belong[N], siz;
long long a[N], b[N], lazy[N];
multiset<long long> s[350];
void modify(int l, int r, int c) {
for (int i = l; i <= min(belong[l] * siz, r); i++) {
s[belong[i]].erase(b[i]);
b[i] += c;
s[belong[i]].insert(b[i]);
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
s[belong[i]].erase(b[i]);
b[i] += c;
s[belong[i]].insert(b[i]);
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) lazy[i] += c;
}
long long query(int l, int r, int c) {
long long maxn = -1;
for (int i = l; i <= min(belong[l] * siz, r); i++) {
if (b[i] + lazy[belong[i]] < c)
maxn = max(maxn, b[i] + lazy[belong[i]]);
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
if (b[i] + lazy[belong[i]] < c)
maxn = max(maxn, b[i] + lazy[belong[i]]);
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
auto it = s[i].lower_bound(c - lazy[i]);
if (it == s[i].begin())
continue;
it--;
maxn = max(maxn, *it + lazy[i]);
}
return maxn;
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
// freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
siz = 1000;
for (int i = 1; i <= n; i++) belong[i] = (i + siz - 1) / siz;
for (int i = 1; i <= n; i++) s[belong[i]].insert(b[i]);
// cout << siz << endl;
// for (int i = 1; i <= n; i++)
// cout << a[i] << " " << belong[i] << endl;
for (int opt, l, r, c, i = 1; i <= n; i++) {
cin >> opt >> l >> r >> c;
if (opt == 0) {
modify(l, r, c);
} else {
cout << query(l, r, c) << endl;
}
}
}
LOJ #6280. 数列分块入门 4
题意
区间加法,区间求和。
题解
lazy[]
存储当前块整块累计加的数 ,sum[]
存储当前块除整块累计加的数的和。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5 + 5000;
int n, m, T, belong[N], siz;
long long a[N], sum[N], lazy[N];
void modify(int l, int r, long long c) {
for (int i = l; i <= min(belong[l] * siz, r); i++) {
a[i] += c;
sum[belong[i]] += c;
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
a[i] += c;
sum[belong[i]] += c;
}
}
// cerr << c * siz << endl;
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) lazy[i] += c;
}
int query(int l, int r, long long c) {
long long tot = 0;
for (int i = l; i <= min(belong[l] * siz, r); i++) {
tot += a[i] + lazy[belong[i]];
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
tot += a[i] + lazy[belong[i]];
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) tot += sum[i] + lazy[i] * siz;
return tot % (c + 1);
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
// freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
siz = 1000;
for (int i = 1; i <= n; i++) {
belong[i] = (i + siz - 1) / siz;
sum[belong[i]] += a[i];
}
for (int opt, l, r, i = 1; i <= n; i++) {
long long c;
cin >> opt >> l >> r >> c;
if (opt == 0) {
modify(l, r, c);
} else if (opt == 1) {
cout << query(l, r, c) << endl;
}
}
}
LOJ #6281. 数列分块入门 5
题意
区间开方,区间求和。
题解
对于当前题的数的大小来看,最大可能出现的数也只要开方5
次就变成1
,所以对于每次开方操作都直接进行逐一的操作,当某一块整块都为一时打上标记当进行查询时此块可以直接跳过。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5 + 5000;
int n, m, T, belong[N], siz;
long long a[N], sum[N];
bool flag[N];
void modify(int l, int r, long long c) {
for (int i = l; i <= min(belong[l] * siz, r); i++) {
sum[belong[i]] -= a[i];
a[i] = sqrt(a[i]);
sum[belong[i]] += a[i];
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
sum[belong[i]] -= a[i];
a[i] = sqrt(a[i]);
sum[belong[i]] += a[i];
}
}
// cerr << c * siz << endl;
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
if (flag[i])
continue;
sum[i] = 0;
flag[i] = 1;
for (int j = (i - 1) * siz + 1; j <= i * siz; j++) {
// cerr << i << " " << j << endl;
a[j] = sqrt(a[j]);
sum[i] += a[j];
if (a[j] > 1)
flag[i] = 0;
}
}
}
int query(int l, int r, long long c) {
long long tot = 0;
for (int i = l; i <= min(belong[l] * siz, r); i++) {
tot += a[i];
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
tot += a[i];
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) tot += sum[i];
return tot;
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
// freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
siz = 1000;
for (int i = 1; i <= n; i++) {
belong[i] = (i + siz - 1) / siz;
sum[belong[i]] += a[i];
}
for (int opt, l, r, i = 1; i <= n; i++) {
long long c;
cin >> opt >> l >> r >> c;
if (opt == 0) {
modify(l, r, c);
} else if (opt == 1) {
cout << query(l, r, c) << endl;
}
}
}
LOJ #6282. 数列分块入门 6
题意
单点插入,单点询问
题解
每一个块用vector
进行维护可以方便的进行不定数组的操作。
当某一块的大小大于一定的数时,马上对所有块进行重构。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
using namespace std;
const int N = 2e5 + 50;
int n, m, T, siz, len, num[N];
vector<int> a[N];
void rebuild(int *num, int tot) {
for (int i = 1; i <= len; i++) a[i].clear();
siz = sqrt(tot);
len = (tot + siz - 1) / siz;
for (int i = 1; i <= tot; i++) {
a[(i + siz - 1) / siz].push_back(num[i]);
}
}
void add(int p, int x) {
for (int i = 1; i <= len; i++) {
if (p > a[i].size()) {
p -= a[i].size();
} else {
a[i].insert(a[i].begin() + (p - 1), x);
if (a[i].size() > 5 * siz) {
int tot = 0;
for (int i = 1; i <= len; i++) {
for (auto j = a[i].begin(); j != a[i].end(); j++) num[++tot] = *j;
}
rebuild(num, tot);
}
return;
}
}
}
int find(int p) {
for (int i = 1; i <= len; i++) {
if (p > a[i].size()) {
p -= a[i].size();
} else {
return a[i][p - 1];
}
}
return -1;
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
// freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> num[i];
rebuild(num, n);
for (int opt, l, r, i = 1; i <= n; i++) {
long long c;
cin >> opt >> l >> r >> c;
if (opt == 0) {
add(l, r);
} else if (opt == 1) {
cout << find(r) << endl;
}
}
}
LOJ #6283. 数列分块入门 7
题意
区间乘法,区间加法
题解
多开tag[]
数组记录当前块所需乘上多少,同样对于两边需要特殊的处理,暴力将边块的每一个数求出真实数据,重置边块的tag[] lazy[]
,然后暴力修改。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5 + 5000;
const int mod = 10007;
int n, m, T, belong[N], siz;
long long a[N], tag[N], lazy[N];
void reset(int p) {
for (int i = (p - 1) * siz + 1; i <= p * siz; i++) a[i] = (a[i] * tag[p] + lazy[p]) % mod;
lazy[p] = 0;
tag[p] = 1;
}
void mul(int l, int r, long long c) {
reset(belong[l]);
for (int i = l; i <= min(belong[l] * siz, r); i++) {
a[i] = (a[i] * c) % mod;
}
if (belong[l] != belong[r]) {
reset(belong[r]);
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
a[i] = (a[i] * c) % mod;
}
}
// cerr << c * siz << endl;
for (int i = belong[l] + 1; i <= belong[r] - 1; i++)
lazy[i] = (lazy[i] * c) % mod, tag[i] = (tag[i] * c) % mod;
}
void add(int l, int r, long long c) {
reset(belong[l]);
for (int i = l; i <= min(belong[l] * siz, r); i++) {
a[i] = (a[i] + c) % mod;
}
if (belong[l] != belong[r]) {
reset(belong[r]);
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
a[i] = (a[i] + c) % mod;
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) lazy[i] = (lazy[i] + c) % mod;
}
int query(int p) { return (lazy[belong[p]] + a[p] * tag[belong[p]]) % mod; }
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
siz = 1000;
for (int i = 1; i <= n; i++) {
belong[i] = (i + siz - 1) / siz;
tag[belong[i]] = 1;
lazy[belong[i]] = 0;
}
for (int opt, l, r, i = 1; i <= n; i++) {
long long c;
cin >> opt >> l >> r >> c;
if (opt == 0) {
add(l, r, c);
} else if (opt == 1) {
mul(l, r, c);
} else if (opt == 2) {
cout << query(r) << endl;
}
}
}
#6284. 数列分块入门 8
题意
间询问等于一个数x
的元素的个数,并将这个区间的所有元素改为 x
题解
flag[]
记录当前块的变化,对于边块的单点修改需要先将边块整块进行调整,然后重置flag
再逐一进行修改。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <string>
#include <queue>
#include <map>
#include <set>
#include <cmath>
#include <climits>
using namespace std;
const int N = 1e5 + 5000;
const int mod = 10007;
int n, m, T, belong[N], siz, a[N], flag[N];
void reset(int p) {
if (flag[p] == -1)
return;
for (int i = (p - 1) * siz + 1; i <= p * siz; i++) a[i] = flag[p];
flag[p] = -1;
}
void modify(int l, int r, int c) {
reset(belong[l]);
for (int i = l; i <= min(belong[l] * siz, r); i++) {
a[i] = c;
}
if (belong[l] != belong[r]) {
reset(belong[r]);
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
a[i] = c;
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) flag[i] = c;
}
int query(int l, int r, int c) {
int tot = 0;
reset(belong[l]);
for (int i = l; i <= min(belong[l] * siz, r); i++) {
tot += (a[i] == c);
}
if (belong[l] != belong[r]) {
reset(belong[r]);
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
tot += (a[i] == c);
}
}
for (int i = belong[l] + 1; i <= belong[r] - 1; i++) {
if (flag[i] == -1) {
for (int j = (i - 1) * siz + 1; j <= i * siz; j++) tot += (a[j] == c);
} else {
tot += siz * (flag[i] == c);
}
}
return tot;
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
siz = sqrt(n);
for (int i = 1; i <= n; i++) {
belong[i] = (i + siz - 1) / siz;
flag[belong[i]] = -1;
}
for (int l, r, c, i = 1; i <= n; i++) {
cin >> l >> r >> c;
cout << query(l, r, c) << endl;
modify(l, r, c);
}
}
LOJ #6285. 数列分块入门 9
题意
询问区间的最小众数。
题解
dp[i][j]
存放从第i
块到第j
块的最小众数,可以简单暴力得出。- 提取出相同元素的下标存在一个数组里,运用
lower_bound upper_boud
快速得出对于某个区间的某个数出现的次数。 - 同样边块进行暴力查找。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <stack>
#include <cstring>
#include <queue>
#include <map>
#include <set>
#include <cmath>
using namespace std;
const int N = 1e5 + 50;
int n, m, a[N], T, b[N], siz, belong[N], rk[N], dp[N][1600], num[N];
vector<int> v[N];
void deal(int x) {
int ans = (x - 1) * siz + 1;
fill(num + 1, num + m + 1, 0);
for (int i = (x - 1) * siz + 1; i <= n; i++) {
num[rk[i]]++;
if (num[rk[i]] > num[rk[ans]] || (num[rk[i]] == num[rk[ans]] && a[i] < a[ans]))
ans = i;
// cout << x << " " << i << " " << ans << endl;
dp[x][belong[i]] = ans;
}
}
int getNum(int l, int r, int p) {
return (upper_bound(v[p].begin(), v[p].end(), r) - lower_bound(v[p].begin(), v[p].end(), l));
}
int query(int l, int r) {
int maxn = dp[belong[l] + 1][belong[r] - 1], num = getNum(l, r, rk[maxn]);
for (int i = l; i <= min(belong[l] * siz, r); i++) {
int tmp = getNum(l, r, rk[i]);
if (tmp > num || (tmp == num && a[maxn] > a[i]))
maxn = i, num = tmp;
// cout << a[i] << " " << tmp << " " << maxn << " " << num << endl;
}
if (belong[l] != belong[r]) {
for (int i = (belong[r] - 1) * siz + 1; i <= r; i++) {
int tmp = getNum(l, r, rk[i]);
if (tmp > num || (tmp == num && a[maxn] > a[i]))
maxn = i, num = tmp;
}
}
return a[maxn];
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("r.txt", "r", stdin);
// freopen("w.txt", "w", stdout);
#endif
ios::sync_with_stdio(false);
cin >> n;
siz = 66;
for (int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i], belong[i] = (i + siz - 1) / siz;
sort(b + 1, b + n + 1);
m = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) {
rk[i] = lower_bound(b + 1, b + m + 1, a[i]) - b;
v[rk[i]].push_back(i);
}
// cout << m << endl;
for (int i = 1; i <= (n + siz - 1) / siz; i++) deal(i);
// for (int i = 1; i <= (n + siz - 1) / siz; i++)
// {
// for (int j = i; j <= (n + siz - 1) / siz; j++)
// {
// cout << dp[i][j] << " ";
// }
// cout << endl;
// }
for (int l, r, i = 1; i <= n; i++) {
cin >> l >> r;
cout << query(l, r) << endl;
}
}