分块入门 LibreOJ分块九题
推荐hzwer大佬的博客:http://hzwer.com/8053.html
初步学习体验就是,分块作为一种复杂度看上去并不优秀实际效率却很优秀的数据结构,在解决某些用其他如线段树等log数据结构非常麻烦的问题时非常好写好用,以后搭配莫队简直是解决大部分区间问题的强力算法
分块本身其实还是挺简单的,这套题总结了一些分块/数据结构的经典问题,很全面,能由浅入深地学习分块的基本使用,分块的特点。切了这几道板子题,分块至少终于不是什么遥不可及的神仙操作了
分块九题一:区间加法,单点查询
这题也作为线段树,树状数组之类的入门题
分块的思想大致都了解了是小块暴力大块更新,究竟是如何做到的,与线段树等有什么相似,这里就以第一题为例介绍分块基本操作
sz = sqrt(n);
sz指定了块的大小为根号n,这里是一个全局变量
b[i] = (i-1)/sz+1;
b数组(belong)表示坐标为i的点属于哪个块,显然这是为了后面直接访问块的编号
区间加上c的add操作如下:
void add(int l, int r, int c){ for (int i = l; i <= min(r, b[l]*sz); i++) a[i] += c; //左端不完整块 if (b[l] != b[r]) for (int i = (b[r]-1)*sz+1; i <= r; i++) a[i] += c; //右端不完整块 for (int i = b[l]+1; i <= b[r]-1; i++) atag[i] += c; //完整块 }
分块的更新与查询基本就是这种模板,先暴力左右两边(要特判左右在不在一个块)然后用一个atag数组给区间打上标记
atag标记类似于线段树的lazy标记,是对一个块(区间)操作的值
那么,更新之后某一点i的结果就是
printf("%d\n", a[i]+atag[b[i]]);
第一题的完整代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmath> 5 using namespace std; 6 7 const int mx = 50010; 8 int sz; 9 int a[mx], b[mx], atag[mx]; 10 11 void add(int l, int r, int c){ 12 for (int i = l; i <= min(r, b[l]*sz); i++) a[i] += c; //左端不完整块 13 if (b[l] != b[r]) 14 for (int i = (b[r]-1)*sz+1; i <= r; i++) a[i] += c; //右端不完整块 15 for (int i = b[l]+1; i <= b[r]-1; i++) atag[i] += c; //完整块 16 } 17 18 int main(){ 19 int n, op, l, r, c; 20 while (scanf("%d", &n) == 1){ 21 sz = sqrt(n); 22 for (int i = 1; i <= n; i++){ 23 scanf("%d", &a[i]); 24 b[i] = (i-1)/sz+1; 25 atag[i] = 0; 26 } 27 for (int i = 1; i <= n; i++){ 28 scanf("%d%d%d%d", &op, &l, &r, &c); 29 if (!op) add(l, r, c); 30 if (op == 1) printf("%d\n", a[r]+atag[b[r]]); 31 } 32 } 33 return 0; 34 }
分块九题二:区间加法,查询小于某值的元素个数
小块暴力 大块标记 整块排序 及时重构
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <cmath> 6 #include <vector> 7 #define LL long long 8 #define INF 0x3f3f3f3f 9 #define debug(x) cout << #x << " = " << x << endl; 10 using namespace std; 11 12 const int mx = 10010; 13 int a[mx*5], b[mx*5]; 14 int atag[mx]; 15 vector<int> G[mx]; 16 int n, sz; 17 18 void reset(int x){ 19 G[x].clear(); 20 for (int i = (x-1)*sz+1; i <= min(n, x*sz); i++) 21 G[x].push_back(a[i]); 22 sort(G[x].begin(), G[x].end()); 23 } 24 25 void add(int l, int r, int x){ 26 for (int i = l; i <= min(r, sz*b[l]); i++) a[i] += x; 27 reset(b[l]); 28 if (b[l] != b[r]) { 29 for (int i = (b[r] - 1) * sz + 1; i <= r; i++) a[i] += x; 30 reset(b[r]); 31 } 32 for (int i = b[l]+1; i <= b[r]-1; i++) atag[i] += x; 33 } 34 35 int query(int l, int r, int c){ 36 int ans = 0; 37 for (int i = l; i <= min(r, sz*b[l]); i++) 38 if (a[i] + atag[b[l]] < c) ans++; 39 if (b[l] != b[r]){ 40 for (int i = (b[r]-1)*sz+1; i <= r; i++) 41 if (a[i] + atag[b[r]] < c) ans++; 42 } 43 for (int i = b[l]+1; i <= b[r]-1; i++){ 44 int k = lower_bound(G[i].begin(), G[i].end(), c-atag[i])-G[i].begin(); 45 ans += k; 46 } 47 return ans; 48 } 49 50 int main(){ 51 scanf("%d", &n); 52 sz = sqrt(n); 53 for (int i = 1; i <= n; i++) { 54 scanf("%d", &a[i]); 55 b[i] = (i - 1) / sz + 1; 56 G[b[i]].push_back(a[i]); 57 } 58 for (int i = 1; i <= b[n]; i++) 59 sort(G[i].begin(), G[i].end()); 60 int op, l, r, x; 61 for (int i = 1; i <= n; i++) { 62 scanf("%d%d%d%d", &op, &l, &r, &x); 63 if (!op) add(l, r, x); 64 else printf("%d\n", query(l, r, x * x)); 65 } 66 return 0; 67 }
分块九题三:区间加法,查询前驱
每块用一个set维护 二分找最大前驱
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <cmath> 6 #include <set> 7 #define LL long long 8 #define INF 0x3f3f3f3f 9 #define debug(x) cout << #x << " = " << x << endl; 10 using namespace std; 11 12 const int mx = 1e5+7; 13 int a[mx], b[mx]; 14 int atag[1010]; 15 set<int> s[1010]; 16 int n, sz; 17 18 void add(int l, int r, int c){ 19 for (int i = l; i <= min(r, sz*b[l]); i++){ 20 s[b[l]].erase(a[i]); 21 a[i] += c; 22 s[b[l]].insert(a[i]); 23 } 24 if (b[l] != b[r]){ 25 for (int i = (b[r]-1)*sz+1; i <= r; i++){ 26 s[b[r]].erase(a[i]); 27 a[i] += c; 28 s[b[r]].insert(a[i]); 29 } 30 } 31 for (int i = b[l]+1; i <= b[r]-1; i++) atag[i] += c; 32 } 33 34 int query(int l, int r, int c){ 35 int ans = -1; 36 for (int i = l; i <= min(r, sz*b[l]); i++){ 37 int v = a[i] + atag[b[l]]; 38 if (v < c) ans = max(ans, v); 39 } 40 if (b[l] != b[r]){ 41 for (int i = (b[r]-1)*sz+1; i <= r; i++){ 42 int v = a[i] + atag[b[r]]; 43 if (v < c) ans = max(ans, v); 44 } 45 } 46 for (int i = b[l]+1; i <= b[r]-1; i++){ 47 int v = c-atag[i]; 48 auto it = s[i].lower_bound(v); 49 if (it == s[i].begin()) continue; 50 --it; 51 ans = max(ans, *it + atag[i]); 52 } 53 return ans; 54 } 55 56 int main(){ 57 scanf("%d", &n); 58 sz = sqrt(n); 59 for (int i = 1; i <= n; i++){ 60 scanf("%d", &a[i]); 61 b[i] = (i-1)/sz+1; 62 s[b[i]].insert(a[i]); 63 } 64 int op, l, r, c; 65 for (int i = 1; i <= n; i++){ 66 scanf("%d%d%d%d", &op, &l, &r, &c); 67 if (!op) add(l, r, c); 68 else printf("%d\n", query(l, r, c)); 69 } 70 return 0; 71 }
分块九题四:区间加法,区间求和
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 #include <cmath> 6 #include <set> 7 #define LL long long 8 #define INF 0x3f3f3f3f 9 #define debug(x) cout << #x << " = " << x << endl; 10 using namespace std; 11 12 const int mx = 50010; 13 LL a[mx]; 14 int b[mx]; 15 LL atag[1010], sum[1010]; 16 int n, sz; 17 18 void add(int l, int r, int c){ 19 for (int i = l; i <= min(r, sz*b[l]); i++){ 20 a[i] += c; 21 sum[b[l]] += c; 22 } 23 if (b[l] != b[r]){ 24 for (int i = (b[r]-1)*sz+1; i <= r; i++){ 25 a[i] += c; 26 sum[b[r]] += c; 27 } 28 } 29 for (int i = b[l]+1; i <= b[r]-1; i++) atag[i] += c; 30 } 31 32 LL query(int l, int r, int c){ 33 LL ans = 0; 34 for (int i = l; i <= min(r, sz*b[l]); i++) 35 ans = (ans + a[i] + atag[b[l]]) % c; 36 if (b[l] != b[r]){ 37 for (int i = (b[r]-1)*sz+1; i <= r; i++) 38 ans = (ans + a[i] + atag[b[r]]) % c; 39 } 40 for (int i = b[l]+1; i <= b[r]-1; i++) 41 ans = (ans + sum[i] + atag[i]*sz) % c; 42 return ans; 43 } 44 45 int main(){ 46 scanf("%d", &n); 47 sz = sqrt(n); 48 for (int i = 1; i <= n; i++){ 49 scanf("%lld", &a[i]); 50 b[i] = (i-1)/sz+1; 51 sum[b[i]] += a[i]; 52 } 53 int op, l, r, c; 54 for (int i = 1; i <= n; i++){ 55 scanf("%d%d%d%d", &op, &l, &r, &c); 56 if (!op) add(l, r, c); 57 else printf("%lld\n", query(l, r, c+1)); 58 } 59 return 0; 60 }
分块九题五:区间开方,区间求和
分块九题六:单点插入,单点查询
分块九题七:区间乘法和加法,区间查询
分块九题八:区间查询,区间修改
分块九题九:区间最小众数