一:分块
分块的思想就是通过合适的划分,将一部分信息预处理并保存下来,用空间来换取时间,其实分块是“优化”的暴力,效率比不上树状数组和线段树,但它更通用和容易实现。
二:例题1
给定一个长度为N(N ≤ 10^5)的数列A,然后有M(M ≤ 10^5)个操作指令。
操作1:格式:1 x y k 含义:将区间[x, y]上的每一个数都加上k
操作2:格式:2 x y 含义:输出区间[x, y]内每个数的和
这道题用线段树和树状数组都可以很优秀的通过,但我们用这道题来练一下分块
三:分析
我们先将数列A分成若干个不超过 ? √n ? (下取整)的段,其中第i段的左端点为(i - 1)? √n ?+1,右端点为min(i*? √n ?, n),例如当n = 10的时候,我们分成4块
我们可以先定义数字sum[i],表示第 i 块的区间和,定义add[i]表示第 i 段的“增量标记”(下面细讲),初始值为0。
1. 对于指令 1 x y k
(1).若x, y都在第 i 块内,则选择暴力更新,把A[x], A[x+1],......A[y-1], A[y]都加上k,同时将sun[i] 更新 为 sum[i] + (y - x + 1) * k
(2). 否则,设x在第p块, y在第q块
①:对于i∈[p+1, q-1], i是一个整块,所以直接add[i] += k,表示第 i 块都加上了k。(优于暴力更新)
②:对于p、q所在不足一整块的,类似于(1)进行暴力更新
2.对于指令 2 x y
(1).若x, y都在第i块内,则(A[x] + A[x+1] + ...... + A[y-1] + A[y]) + add[i] * (y - x + 1)就是答案
(2).否则设x在第p块, y在第q块, 令Ans = 0;
①:对于i∈[p+1, q-1], i是一个整块,Ans += sum[i] + add[i] * len[i];// len[i] 是第i块的长度
②:对于p、q所在不足一整块的,类似于(1)进行计算
对于t, 我们令 t = ? √n ?, len = n / t,由于n不一定是完全平方数,所以就要新开一块的操作(Code中会说明)。由于块长块数都近似于O(√n),所以时间复杂度为O((n + m) * √n)
Code:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 const int maxn = 1e5 + 10; 6 7 // 分块求解 O((n+m) * √n) 8 9 LL Read(); 10 11 LL n, m, t; 12 LL val[maxn], sum[maxn], add[maxn]; 13 int L[maxn], R[maxn], pos[maxn]; 14 15 inline void change(int l, int r, LL x) { 16 int p = pos[l], q = pos[r]; // l r所在那一块 17 if(p == q) { // 在同一块 18 for(int i=l; i<=r; i++) val[i] += x; // 直接更新 19 sum[p] += x * (r - l + 1); // 更新本块的和 20 } 21 else { // 不在同一块 22 for(int i=p+1; i<=q-1; ++i) add[i] += x; // p ~ q之间的整块 23 for(int i=l; i<=R[p]; ++i) val[i] += x; // 更新 p所在的非整块 24 sum[p] += x * (R[p] - l + 1); // 更新sum 25 for(int i=L[q]; i<=r; ++i) val[i] += x; //更新 q所在的非整块 26 sum[q] += x * (r - L[q] + 1); // 更新sum 27 } 28 } 29 30 inline LL ask(int l, int r) { 31 int p = pos[l], q = pos[r]; 32 LL Ans = 0; 33 if(p == q) { // 在同一块 34 for(int i=l; i<=r; ++i) Ans += val[i]; // 暴力求和 35 Ans += add[p] * (r - l + 1); // 加上此块总共加的 36 } 37 else { 38 for(int i=p+1; i<=q-1; ++i) // 加上pq所在块之间的整块 39 Ans += sum[i] + add[i] * (R[i] - L[i] + 1); // 加上sum和此块总共加的 40 for(int i=l; i<=R[p]; ++i) Ans += val[i]; //加上p所在块的一些元素 41 Ans += add[p] * (R[p] - l + 1); 42 for(int i=L[q]; i<=r; ++i) Ans += val[i];//加上p所在块的一些元素 43 44 Ans += add[q] * (r - L[q] + 1); 45 } 46 return Ans; 47 } 48 49 int main() { 50 n = Read(), m = Read(); 51 for(int i=1; i<=n; ++i) val[i] = Read(); 52 53 // 分块 54 t = sqrt(n); 55 for(int i=1; i<=t; ++i) { 56 L[i] = (i-1) * t + 1; // 第i块的左断电 57 R[i] = i * t; // 第i块的右端点 58 } 59 if(R[t] < n) { // 没有分完, 60 t++; // 新加一组 61 L[t] = R[t-1]+1; // 新的一组左端点为上一组右端点的下一个 62 R[t] = n; // 右端点为n 63 } 64 65 //预处理 66 for(int i=1; i<=t; ++i) { // 枚举每一块 开始预处理 67 for(int j=L[i]; j<=R[i]; ++j) { 68 pos[j] = i; // 记录每一个元素 69 sum[i] += val[j]; // 计算每一块的和 70 } 71 } 72 73 // solve 74 while(m --) { 75 int f, x, y; scanf("%d%d%d", &f, &x, &y); 76 if(f == 1) { // 77 LL z = Read(); 78 change(x, y, z); 79 } 80 else { 81 //printf("%lld %lld\n", x, y); 82 LL Ans = ask(x, y); 83 printf("%lld\n", Ans); 84 } 85 } 86 return 0; 87 } 88 // 快读 89 inline LL Read() { 90 LL x = 0, f = 1; char ch = getchar(); 91 while(!isdigit(ch)) {if(ch == ‘-‘) f = -1; ch = getchar(); } 92 while(isdigit(ch)) { x = x * 10 + (ch-48), ch = getchar(); } 93 return x*f; 94 }
四:例题2
题目描述
众所周知,模数的hash会产生冲突。例如,如果模的数p=7
,那么4
和11
便冲突了。
B君对hash冲突很感兴趣。他会给出一个正整数序列value[]
。
自然,B君会把这些数据存进hash池。第value[k]
会被存进(k%p)
这个池。这样就能造成很多冲突。
B君会给定许多个p
和x
,询问在模p
时,x
这个池内数的总和
。
另外,B君会随时更改value[k]
。每次更改立即生效。
保证1<=p<n1<=p<n.
原文地址:https://www.cnblogs.com/Marginalin/p/9777061.html