为了方便叙述和总结,对于hzwer大佬中的分块1~9题型我会进行重新排序。
题型1: 区间加法/区间开方/区间乘法/区间求和
对于这类分块问题,我们应该采取“大段预处理维护、局部暴力朴素”的思路!
分块入门1 区间加法 + 单点查询
整块Block修改:只需要维护一个add标记
局部小块修改:一个一个直接修改即可
单点查询:直接输出 Add + Num[Pos]即可
#include <bits/stdc++.h> typedef long long ll; const int MAXN = 50005; //Block是每块的大小 Size是有多少块 int n, Block, Size; int Belong[MAXN], L[MAXN], R[MAXN]; ll Num[MAXN], Add[MAXN]; void Charge(int l,int r,ll Val){ int X = Belong[l]; int Y = Belong[r]; if(X == Y){ //如果属于同一块 直接暴力修改 for(int i = l;i <= r; i++) Num[i] += Val; } else{ //先处理整块 for(int i = X + 1; i <= Y - 1; i++) Add[i] += Val; //再处理不完整的区域 for(int i = l; i <= R[X]; i++) Num[i] += Val; for(int i = r; i >= L[Y]; i--) Num[i] += Val; } } void preBlock(){ Block = sqrt(n); Size = n / Block; if(n % Block != 0) Size++; //L[i]是第i块的左端点 R[i]是第i块的右端点 for(int i = 1; i <= Size; i++){ L[i] = (i - 1) * Block + 1; R[i] = i * Block; } R[Size] = n; //Belong[i] 是 判断数字i属于哪一块 for(int i = 1; i <= n; i++) Belong[i] = (i - 1) / Block + 1; } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &Num[i]); preBlock(); while(n--){ int opt, l, r, c; scanf("%d %d %d %d", &opt, &l, &r, &c); if(opt == 0) Charge(l, r, c); else printf("%lld\n", Add[Belong[r]] + Num[r]); } return 0; }
分块入门4 区间加法 + 区间求和
这道题目相比较上道题目,我们需要再多预处理一个块的区间和数组Sum,方便修改查询。
整块Block修改:维护一个Add标记
局部小块修改:一个一个直接修改,并同时对当前大块Block的区间和数组进行修改。
整块Block求和:直接用 Sum[i]数组 和 Add[i] * Block 求出答案。
局部小块求和:一个一个加和,并加上Add[i] * 当前块的影响长度。
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> typedef long long ll; const int MAXN = 100005; int n, m, Block, Size; int Belong[MAXN], L[MAXN], R[MAXN]; ll Num[MAXN], Add[MAXN], Sum[MAXN]; void Charge(int l,int r,ll Val){ int X = Belong[l]; int Y = Belong[r]; if(X == Y){ for(int i = l;i <= r; i++) Num[i] += Val; Sum[X] += (r - l + 1) * Val; } else{ for(int i = X + 1; i <= Y - 1; i++) Add[i] += Val; for(int i = l; i <= R[X]; i++) Num[i] += Val; Sum[X] += (R[X] - l + 1) * Val; for(int i = r; i >= L[Y]; i--) Num[i] += Val; Sum[Y] += (r - L[Y] + 1) * Val; } } ll Ask(int l,int r){ int X = Belong[l]; int Y = Belong[r]; ll ans = 0; if(X == Y){ for(int i = l; i <= r; i++) ans += Num[i]; ans += Add[X] * (r - l + 1); } else{ for(int i = X + 1; i <= Y - 1; i++) ans += (Sum[i] + Add[i] * Block); for(int i = l; i <= R[X]; i++) ans += Num[i]; ans += Add[X] * (R[X] - l + 1); for(int i = r; i >= L[Y]; i--) ans += Num[i]; ans += Add[Y] * (r - L[Y] + 1); } return ans; } void preBlock(){ Block = sqrt(n); Size = n / Block; if(n % Block != 0) Size++; for(int i = 1; i <= Size; i++){ L[i] = (i - 1) * Block + 1; R[i] = i * Block; } R[Size] = n; for(int i = 1; i <= n; i++){ Belong[i] = (i - 1) / Block + 1; Sum[Belong[i]] += Num[i]; } } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%lld", &Num[i]); preBlock(); while(n--){ ll opt, l, r, c; scanf("%lld%lld%lld%lld", &opt, &l, &r, &c); if(opt == 0) Charge(l, r, c); else printf("%lld\n", Ask(l, r) % (c + 1)); } return 0; }
分块入门5 区间开方 + 区间求和
本题要用到一个很重要的性质,就是一个数多次(最多五次)开方并向下取整之后一定会变成1.所以本题我们需要预处理块内数组Sum,并对整块进行判断是否全为1.
整块Block开方:先判断当前块是否都为1,如果是直接跳过,否则对于块内每个数都进行开方操作并更新区间和Sum.
局部小块开方:直接暴力开方,并同时修改当前块的区间和Block[i]
整块Block求和: 直接累加每个块的区间和Sum.
局部小块求和:直接暴力计算
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> typedef long long ll; const int MAXN = 50005; int n, m, Block, Size; int Belong[MAXN], L[MAXN], R[MAXN]; ll Num[MAXN], Sum[MAXN]; bool not_cal[MAXN]; void Judge(int Pos){ if(not_cal[Pos] == true) return; not_cal[Pos] = 1; Sum[Pos] = 0; for(int i = L[Pos]; i <= R[Pos]; i++){ Num[i] = sqrt(Num[i]); Sum[Pos] += Num[i]; if(Num[i] > 1) not_cal[Pos] = 0; } } void sectionSqrt(int l,int r){ int X = Belong[l]; int Y = Belong[r]; if(X == Y){ for(int i = l;i <= r; i++){ Sum[X] -= Num[i]; Num[i] = sqrt(Num[i]); Sum[X] += Num[i]; } } else{ for(int i = l; i <= R[X]; i++){ Sum[X] -= Num[i]; Num[i] = sqrt(Num[i]); Sum[X] += Num[i]; } for(int i = r; i >= L[Y]; i--){ Sum[Y] -= Num[i]; Num[i] = sqrt(Num[i]); Sum[Y] += Num[i]; } for(int i = X + 1; i <= Y - 1; i++) Judge(i); } } ll Ask(int l,int r){ int X = Belong[l]; int Y = Belong[r]; ll ans = 0; if(X == Y) for(int i = l; i <= r; i++) ans += Num[i]; else{ for(int i = l; i <= R[X]; i++) ans += Num[i]; for(int i = r; i >= L[Y]; i--) ans += Num[i]; for(int i = X + 1; i <= Y - 1; i++) ans += Sum[i]; } return ans; } void preBlock(){ Block = sqrt(n); Size = n / Block; if(n % Block != 0) Size++; for(int i = 1; i <= Size; i++){ L[i] = (i - 1) * Block + 1; R[i] = i * Block; } R[Size] = n; for(int i = 1; i <= n; i++){ Belong[i] = (i - 1) / Block + 1; Sum[Belong[i]] += Num[i]; } } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%lld", &Num[i]); preBlock(); while(n--){ ll opt, l, r, c; scanf("%lld%lld%lld%lld", &opt, &l, &r, &c); if(opt == 0) sectionSqrt(l, r); else printf("%lld\n", Ask(l, r)); } return 0; }
分块入门7 区间乘法 + 区间加法 + 单点查询
很容易想到本题需要预处理区间乘法标记Mul 和 区间加法标记Add.但本题主要的难点在于 乘法比加法的优先级要更高,所以很多操作实现细节需要特别注意。
首先是在处理局部问题的时候 需要将标记直接下放,因为如果不这么做的话。 假设对于当前数 Num[Pos] 加上一个数 Val,那么本来这个数应该 Num[Pos] * Mul * Val + Add * Val 但是会计算成 (Num[Pos] + Val ) * Mul + Add。
第二点是当出现 整块先加Val1后乘Val2的情况时,需要用Val2去乘上加法标记。 因为对于当前区间的数 Num,在进行(Num + Val1) * Val2的操作后,每个数都变成了 Num * Val2 + Val1* Val2。
整块Block乘法/加法: 加法直接加,乘法需要特殊处理一下
局部乘法/加法: 先把标记下放后,再乘/加
单点查询: 对于当前数只需要 ( Num[i] * Mul[belong[i]] + Add[belong[i]] ) % MOD 即可。
#include <bits/stdc++.h> typedef long long ll; const int MAXN = 100005; const ll MOD = 10007; int n, Block, Size; int Belong[MAXN], L[MAXN], R[MAXN]; ll Num[MAXN], Add[MAXN], Mul[MAXN]; inline ll Read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void updateBlock(int Pos){ for(int i = L[Pos];i <= R[Pos]; i++){ Num[i] = (Num[i] * Mul[Pos] + Add[Pos]) % MOD; } Mul[Pos] = 1; Add[Pos] = 0; } void sectionCharge(int Opt, int l, int r, ll Val){ int X = Belong[l]; int Y = Belong[r]; if(X == Y){ updateBlock(X); for(int i = l;i <= r; i++){ if(Opt == 0) Num[i] += Val; else Num[i] *= Val; Num[i] %= MOD; } } else{ updateBlock(X); for(int i = l; i <= R[X]; i++){ if(Opt == 0) Num[i] += Val; else Num[i] *= Val; Num[i] %= MOD; } updateBlock(Y); for(int i = r; i >= L[Y]; i--){ if(Opt == 0) Num[i] += Val; else Num[i] *= Val; Num[i] %= MOD; } for(int i = X + 1; i <= Y - 1; i++){ if(Opt == 0) Add[i] = (Add[i] + Val) % MOD; else{ Mul[i] = (Mul[i] * Val) % MOD; Add[i] = (Add[i] * Val) % MOD; } } } } void preBlock(){ Block = sqrt(n); Size = n / Block; if(n % Block != 0) Size++; for(int i = 1; i <= Size; i++){ L[i] = (i - 1) * Block + 1; R[i] = i * Block; } R[Size] = n; for(int i = 1; i <= n; i++){ Belong[i] = (i - 1) / Block + 1; Mul[i] = 1; } } int main(){ //freopen("a1.in","r",stdin); //freopen("my.out", "w", stdout); scanf("%d", &n); for(int i = 1; i <= n; i++){ Num[i] = Read(); Num[i] %= MOD; } preBlock(); while(n--){ ll opt, l, r, c; opt = Read(); l = Read(); r = Read(); c = Read(); if(opt == 2) printf("%lld\n",(Num[r] * Mul[Belong[r]] + Add[Belong[r]]) % MOD); else sectionCharge(opt, l, r, c); } return 0; }
题型2: 查询区间前驱 / 查询区间特定数个数
分块入门2 区间加法 + 询问区间小于x的数的个数
为了能在整块Block中快速查询小于x的数我们可以保证每块Block块内的有序性,从而快速二分查找。如果你很想当然地以为在每次区间加法操作后直接sort排序就行了,那么就会有问题!!! 因为排序后你打乱了原序列的顺序,导致你影响了之后的区间加和操作。所以这就要求我们多开一个vector数组存储并维护它的块内有序性,而每次加法操作都是在原序列进行。
整块Block加法:维护Add标记即可
局部加法: 一个一个暴力加和,但是加完记得排序保证其块内有序性。
整块Block查询: 直接二分查找
局部查询: 一个一个判断即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 50005; int n, Block, Size; int Belong[MAXN], L[MAXN], R[MAXN]; ll Num[MAXN], Add[MAXN]; vector<int>Re[2333]; void ReSort(int Pos){ Re[Pos].clear(); for(int i = L[Pos]; i <= R[Pos]; i++) Re[Pos].push_back(Num[i]); sort(Re[Pos].begin(), Re[Pos].end()); /* printf("Debug Time:"); for(auto Out : Re[Pos]) printf("%d ", Out); printf("\n"); */ } void Charge(int l,int r,ll Val){ int X = Belong[l]; int Y = Belong[r]; if(X == Y){ for(int i = l;i <= r; i++) Num[i] += Val; ReSort(X); } else{ for(int i = X + 1; i <= Y - 1; i++) Add[i] += Val; for(int i = l; i <= R[X]; i++) Num[i] += Val; for(int i = r; i >= L[Y]; i--) Num[i] += Val; ReSort(X); ReSort(Y); } } ll ask(int l,int r,ll Val){ int X = Belong[l]; int Y = Belong[r]; ll ans = 0; if(X == Y){ for(int i = l;i <= r; i++) if(Num[i] < Val - Add[X]) ans++; } else{ for(int i = X + 1; i <= Y - 1; i++) ans += lower_bound(Re[i].begin(), Re[i].end(), Val - Add[i]) - Re[i].begin(); for(int i = l; i <= R[X]; i++) if(Num[i] < Val - Add[X]) ans++; for(int i = r; i >= L[Y]; i--) if(Num[i] < Val - Add[Y]) ans++; } return ans; } void preBlock(){ //sort(Num + 1, Num + 1 + n); Block = sqrt(n); Size = n / Block; if(n % Block != 0) Size++; for(int i = 1; i <= Size; i++){ L[i] = (i - 1) * Block + 1; R[i] = i * Block; } R[Size] = n; for(int i = 1; i <= n; i++) Belong[i] = (i - 1) / Block + 1; for(int i = 1; i <= Size; i++) ReSort(i); } int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%lld", &Num[i]); preBlock(); while(n--){ ll opt, l, r, c; scanf("%lld %lld %lld %lld", &opt, &l, &r, &c); if(opt == 0) Charge(l, r, c); else printf("%lld\n", ask(l, r, c * c)); } return 0; }
待更