分块思想是什么?(引自 OI-wiki)
分块的时间复杂度主要取决于分块的块长,一般可以通过均值不等式求出某个问题下的最优块长,以及相应的时间复杂度。
分块是一种很灵活的思想,相较于树状数组和线段树,分块的优点是通用性更好,可以维护很多树状数组和线段树无法维护的信息。
当然,分块的缺点是渐进意义的复杂度,相较于线段树和树状数组不够好。
习题练习
1.守墓人
题目大意
原题见Luogu P2357 守墓人
有一个长为 $n$ 的序列,一共有 $m$ 此操作,
每次操作会输入 $opt$,对应操作如下:
1.$opt = 1$,将 $[l,r]$ 这个区间所有数的值增加 $k$。
2.$opt = 2$, 将 $[1,1]$ 值增加 $k$
3.$opt = 3$,将 $[1,1]$ 值减少 $k$
4.$opt = 4$,统计 $[l,r]$ 的区间和
5.$opt = 5$,求 $[1,1]$ 的区间和
代码(思路见注释)
点击查看代码
#include <bits/stdc++.h>
#define PII pair <int, int>
#define LL long long
#define DB double
#define ST stringusing namespace std;const int N = 200010;
int n, m;
int idx[N], klen;
// 所在位置公式(下表为 i):(i - 1) / len + 1(len 为块长)
int opt, l, r;
LL a[N], tag[N], sum[N], x; // 记得开 long long // 计算一个块的左端点
int L(int x)
{ return klen * (x - 1) + 1; }// 计算一个块的右端点
int R(int x)
{ return min(n, x * klen); // 记得取 min,因为最后一个块不一定是整个
}void add(int l, int r, int x)
{// 如果左右段点均在同一个块中,就暴力处理 if(idx[l] == idx[r]){for(int i = l; i <= r; i ++ )a[i] += x, sum[idx[i]] += x;}else{// 将左边的散块暴力处理 for(int i = l; i <= R(idx[l]); i ++ )a[i] += x, sum[idx[i]] += x;// 统一处理中间的整块 for(int i = idx[l] + 1; i <= idx[r] - 1; i ++ ){tag[i] += x; // 懒惰标记,统计散块和的时候加上sum[i] += (R(i) - L(i) + 1) * x;}// 将右边的散块暴力处理for(int i = L(idx[r]); i <= r; i ++ )a[i] += x, sum[idx[i]] += x;}
}LL count(int l, int r)
{LL res = 0;if(idx[l] == idx[r]){for(int i = l; i <= r; i ++ )res += a[i] + tag[idx[i]]; // 记得加上懒惰标记中的值 }else{for(int i = l; i <= R(idx[l]); i ++ )res += a[i] + tag[idx[i]];for(int i = idx[l] + 1; i <= idx[r] - 1; i ++ )res += sum[i];for(int i = L(idx[r]); i <= r; i ++ )res += a[i] + tag[idx[i]];}return res;
}signed main()
{scanf("%d%d", &n, &m);// 预处理出块长 klen = sqrt(n);for(int i = 1; i <= n; i ++ ){scanf("%lld", &a[i]);idx[i] = (i - 1) / klen + 1;sum[idx[i]] += a[i];}for(int i = 1; i <= m; i ++ ){scanf("%d", &opt);if(opt == 1){scanf("%d%d%d", &l, &r, &x);add(l, r, x);}if(opt == 2)scanf("%d", &x), add(1, 1, x);if(opt == 3)scanf("%d", &x), add(1, 1, -x);if(opt == 4){scanf("%d%d", &l, &r);printf("%lld\n", count(l, r));}if(opt == 5)printf("%lld\n", count(1, 1));}return 0;
}
2.蒲公英
题目大意
见 AcWing 249. 蒲公英
做题思路
用 s[p][x] 记录第 1~p 块中数 x 出现的次数
(tip:因为 x 比较大,所以要将 x 离散化)。
所以数 x 在 p~q 块中出现次数即为 s[p][x]-s[q-1][x]。
再用 f[p][q] 维护在 p~q 块中的众数。
对于一段(l~r),若 l 和 r 在同一块,就暴力记录数出现的次数。
否则枚举左边散数和右边散数,如果这一个数是第一次出现,
那就把它在整块中出现的次数加上去(即 s[id[r]-1][x]-s[id[l]][x]),
最后把它与整快中的众数(即 f[id[l]+1][id[r]-1])作比较即可。
最后记得输出离散前的答案。
复杂度:O(Nsqrt(N))。
代码
点击查看代码
#include <bits/stdc++.h>
#define PII pair <int, int>
#define LL long long
#define DB double
#define ST string
#define endl '\n'using namespace std;const int N = 40010, M = 310;
int n, m, len, tot, a[N];
int id[N], s[M][N], f[M][M];
vector <int> mp1;
int l, r, lst;
int num, cnt, numx, cntx, c[N];
unordered_map <int, int> mp2;int L(int x)
{ return (x - 1) * len + 1; }int R(int x)
{ return min(n, x * len); }int query(int l, int r)
{int num, cnt = 0;if(id[l] == id[r]){for(int i = l; i <= r; i ++ ){c[a[i]] ++ ;if(c[a[i]] > cnt || (c[a[i]] == cnt && a[i] < num))cnt = c[a[i]], num = a[i];}for(int i = l; i <= r; i ++ )c[a[i]] = 0;}else{num = f[id[l] + 1][id[r] - 1];cnt = s[id[r] - 1][num] - s[id[l]][num];for(int i = l; i <= R(id[l]); i ++ ){if(c[a[i]] == 0)c[a[i]] += s[id[r] - 1][a[i]] - s[id[l]][a[i]];c[a[i]] ++ ;if(c[a[i]] > cnt || (c[a[i]] == cnt && a[i] < num))cnt = c[a[i]], num = a[i];}for(int i = L(id[r]); i <= r; i ++ ){if(c[a[i]] == 0)c[a[i]] += s[id[r] - 1][a[i]] - s[id[l]][a[i]];c[a[i]] ++ ;if(c[a[i]] > cnt || (c[a[i]] == cnt && a[i] < num))cnt = c[a[i]], num = a[i];}for(int i = l; i <= R(id[l]); i ++ )c[a[i]] = 0;for(int i = L(id[r]); i <= r; i ++ )c[a[i]] = 0;}return mp1[num];
}signed main()
{scanf("%d%d", &n, &m);len = sqrt(n);for(int i = 1; i <= n; i ++ ){scanf("%d", &a[i]);mp1.push_back(a[i]);id[i] = (i - 1) / len + 1;}sort(mp1.begin(), mp1.end());tot = unique(mp1.begin(), mp1.end()) - mp1.begin();for(int i = 0; i < tot; i ++ )mp2[mp1[i]] = i;for(int i = 1; i <= n; i ++ )a[i] = mp2[a[i]];for(int i = 1; i <= id[n]; i ++ )for(int j = L(i); j <= R(i); j ++ )for(int k = i; k <= id[n]; k ++ )s[k][a[j]] ++ ;for(int i = 1; i <= id[n]; i ++ ){for(int j = i; j <= id[n]; j ++ ){num = f[i][j - 1];cnt = s[j][num] - s[i - 1][num];for(int k = L(j); k <= R(j); k ++ ){numx = a[k];cntx = s[j][numx] - s[i - 1][numx];if(cntx > cnt || (cntx == cnt && numx < num))num = numx, cnt = cntx;}f[i][j] = num;}}while(m -- ){scanf("%d%d", &l, &r);l = ((l + lst - 1) % n) + 1;r = ((r + lst - 1) % n) + 1;if(l > r) swap(l, r);lst = query(l, r);printf("%d\n", lst);}return 0;
}
3.磁力块
题目大意
见 AcWing 250. 磁力块
做题思路
发现:可以将 x 和 y 转化为与 x0 和 y0 的距离,
但是为了避免精度问题,可以不对距离取平方根,而将所有的磁吸半径取平方(!!!)。
所以可以先将每一个磁力块按它与原点(即小取酒的位置)的距离先按从低到高排序,
对于每一块(块长根号N)再按它内部的磁块重量从小到大排序,并记录这个段中的最重磁块重量。
处理:
可以使用 BFS,在队列中存储未被使用的磁力块,每一次取出队头的磁力块属性。
接着从前到后枚举所有的块,若这个块的最大质量已经超过了当前磁力块的磁性,
那么就退出循环,进行散块操作
(即从前到后遍历整个块中的磁力块,若可以取,则标记为已取并加入磁力块队列),
否则就从上一次去到的磁力块的后面(记录在 lst[i] 中)的块遍历,
若该块的质量大于当前磁力块的磁力,那么退出循环(记得边做边更改 lst[i])。
tip:判断 这个块的最大质量已经超过了当前磁力块的磁性 需要放在操作之前。
复杂度:O(Nsqrt(N))。
代码
点击查看代码
#include <bits/stdc++.h>
#define PII pair <int, int>
#define int long long
#define DB double
#define y0 zzxdalaousing namespace std;const int N = 250010, M = 510;
int n, t, k, tot;
int x0, y0;
int mx[N], lst[N];
bool used[N];struct Node
{int w, m, p, r;
}a[N];int L(int x)
{ return (x - 1) * t + 1; }int R(int x)
{ return min(x * t, n); }signed main()
{scanf("%lld%lld", &x0, &y0);scanf("%lld%lld%lld", &a[0].p, &a[0].r, &n);// 记得平方a[0].r = a[0].r * a[0].r;t = sqrt(n), k = (n - 1) / t + 1;for(int i = 1, x, y; i <= n; i ++ ){scanf("%lld%lld", &x, &y);// 将 x 和 y 转化为与小取酒的距离a[i].w = pow(x - x0, 2) + pow(y - y0, 2);scanf("%lld%lld%lld", &a[i].m, &a[i].p, &a[i].r);// 记得平方a[i].r = a[i].r * a[i].r; }// 按距离排序sort(a + 1, a + 1 + n, [&](Node x, Node y){ return x.w < y.w; });// 初始化for(int i = 1; i <= k; i ++ ){lst[i] = L(i) - 1;mx[i] = a[R(i)].w;sort(a + L(i), a + 1 + R(i), [&](Node x, Node y){ return x.m < y.m; });}queue <Node> q;q.push(a[0]);while(q.size()){Node frn = q.front(); q.pop();int i;// 整块处理for(i = 1; i <= k; i ++ ){// 记得将这句话放在 for 循环之前if(mx[i] > frn.r) break;for(int j = lst[i] + 1; j <= R(i); j ++ ){if(a[j].m > frn.p) break;lst[i] = j;if(used[j]) continue;used[j] = 1;q.push(a[j]);tot ++ ;}}// 散块处理for(int j = lst[i] + 1; j <= R(i); j ++ ){if(a[j].m <= frn.p && !used[j] && a[j].w <= frn.r)used[j] = 1, tot ++ , q.push(a[j]);}}printf("%lld\n", tot);return 0;
}