【OI】简单的分块
介绍下简单的分块:
当我们遇到区间类问题的时候,如何保证我们快速而高效地完成操作?
答案是线段树分块。
所谓分块,就是把一个序列分成许多块分别维护。是不是想起了树状数组 这样能大大提高效率:
例如,我们需要查询l,r中所有元素的和
利用分块,我们可以把1 2 3 4 5 6 7 8 9 10
分为
[1 2 3] [4 5 6] [7 8 9] [10]
比如,我想要3~10的元素和。这是,我们拿出3~10的区间:
...[3] [4 5 6] [7 8 9] [10]
而我们已经预处理了 [4 5 6]与 [7 8 9]的区间和,我们只需要算出两端的和就可以。这样,就可以大大提高查询的速度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 100010, MaxB = 1010; int n, B, q, Cnt; int a[MaxN], Left[MaxB], Right[MaxB], Pos[MaxN]; long long sum[MaxN]; int main() { scanf ( "%d" , &n); for ( int i = 1; i <= n; i++) scanf ( "%d" , &a[i]); // 分块预处理 B = max(( int ) sqrt (n), 1); for ( int i = 1; i <= n; i++) { if (i % B == 1 % B) Cnt++; // 每块块首 Cnt++ Pos[i] = Cnt; // i 处在哪一块 if (Left[Cnt] == 0) Left[Cnt] = i; // 第Cnt块的左边 Right[Cnt] = i; // 第Cnt块的右边 } // for (int i = 1; i <= n; i++) printf("%d ", Pos[i]); puts(""); // for (int i = 1; i <= Cnt; i++) printf("%d %d\n", Left[i], Right[i]); for ( int i = 1; i <= Cnt; i++) for ( int j = Left[i]; j <= Right[i]; j++) sum[i] += a[j]; // sum[i] 表示的是第i块的和 // 处理询问 scanf ( "%d" , &q); // a[l] + a[l + 1] + ... + a[r] 变成 sum[r] - sum[l - 1] // 这样我们可以只用求 l=1 的情况 // 但是我现在不这样做 for ( int i = 1; i <= q; i++) { int l, r; scanf ( "%d %d" , &l, &r); long long ans = 0; if (Pos[l] == Pos[r]) { // 1 l,r 在同一个块内 // [ l r ] for ( int j = l; j <= r; j++) ans += a[j]; } else { // 2 l,r 不在同一个块内 // [ l ] [ ] [ ] [r ] for ( int j = l; j <= Right[Pos[l]]; j++) ans += a[j]; for ( int j = Right[Pos[l]] + 1; j <= Left[Pos[r]] - 1; j++) ans += sum[j]; for ( int j = Left[Pos[r]]; j <= r; j++) ans += a[j]; } printf ( "%lld\n" , ans); } return 0; } |
(不是我写的,但是的确十分简洁易懂对吧)
例题:luogu P3203,LCT板子题可是可以用分块来过

#include<iostream> #include<vector> #include<cstdio> #include<queue> #include<map> #include<cstdlib> #include<cmath> #include<algorithm> #include<set> #include<cstring> using namespace std; typedef long long ll; const ll INF=99999999; const int MaxN = 200010; int a[MaxN]; struct nd{ int t,to;//弹几次出此块 int from;//属于第几个块 }t[MaxN]; int n,m,I,J; int sqr,cnt = -1; int Left[MaxN],Right[MaxN]; void findT(int x,int right,int &t,int &to){ int re = 0; int i = x; for(;i <= right;){ int ad = a[i]; i += ad; re++; } t = re; to = i; //printf("需要%d步跳出,跳到%d\n",t,to); } inline int Max(int a,int b){ if(a>b) return a; return b; } int main() { //freopen("testdata.in","r",stdin); //freopen("testdata.out","w",stdout); scanf("%d",&n); for(int i = 0;i < n; i++){ scanf("%d",&a[i]); } sqr = Max(sqrt(n),1); /*for(int i = 0;i < n; i++){ if(i%sqr == 0%sqr) { cnt++; Left[cnt-1] = i; //printf("第%d个块左边是%d\n",cnt-1,i); } Right[cnt-1] = i; t[i].from = cnt-1; //printf("右边界:%d\n",sqr*cnt-1); findT(i,sqr*cnt-1,t[i].t,t[i].to); }*/ for(int i = 0;i < n; i++){ if(i%sqr == 0%sqr){ Left[++cnt] = i; } t[i].from = cnt; Right[cnt] = i; } for(int i = n-1;i >= 0;i--){ t[i].to = i+a[i]; if(t[i].to >= Right[t[i].from]) t[i].t = 1; else t[i].t=t[t[i].to].t+1,t[i].to=t[t[i].to].to; //printf("%d\n",t[i].to); } /* for(int i = 0;i < n; i++){ printf("第%d个元素:跳%d次跳出此块(%d~%d)到%d,属于第%d个块\n",i,t[i].t,Left[t[i].from],Right[t[i].from],t[i].to,t[i].from); }*/ scanf("%d",&m); for(int i = 0;i < m; i++){ scanf("%d%d",&I,&J); int k; if(I == 2){ scanf("%d",&k); a[J] = k; for(int j = Right[t[J].from];j >= Left[t[J].from]; j--){ t[j].to = j+a[j]; if(t[j].to >= Right[t[J].from]) t[j].t = 1; else t[j].t=t[t[j].to].t+1,t[j].to=t[t[j].to].to; } } else{ int ans = 0; while(J < n){ ans += t[J].t; J = t[J].to; } printf("%d\n",ans); } } return 0; }
利用分块思想,每一个元素维护跳几次跳出这个块以及跳到哪里。
这样,就可以大大提高查询的速度,但是问题在于如何O(n)的预处理。
预处理:从后往前枚举,就好像穿起一条链。比如:
设t[i]为需要跳t[i]次跳出某个块,to[i]为跳出某个块到to[i]。
假设这个序列为:
... 1 1
第n个元素,处于第x块,则需要1次跳出块到n+a[i]。(t[i] = 1,to[i] = 4)
第n-1个元素,处于第x块(假设处于同一个块),则需要2次跳出块到4。(t[i] = t[to[i]]+1 = 2,to[i] = to[to[i]],相信不难理解)
以此类推。
这样,只要在修改的时候对于修改元素所在的块进行我们写好的预处理。
分类:
OI || algorithm
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!