news 2026/10/29 10:34:19

数据结构与算法:分块(一):块状数组、块状链表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据结构与算法:分块(一):块状数组、块状链表

前言

这玩意儿也太妙了!!

一、内容

对于 n 长度的数组,考虑将其平均划分为块,那么就有块数和每个块内的长度都是的级别,所以此时就能实现遍历所有块和块内遍历一遍的复杂度都是的了。

在维护信息时,块内就可以暴力维护,整块直接修改标签即可。那么在处理时,就只需要对左右散块暴力,中间块遍历即可。

二、适用场景

当区间信息不可合并或者很难合并时,那么就无法使用线段树了。而此时往往分块比较容易解决,因为块内信息是暴力维护的。除此之外,分块在思维上比其他正解是要好想的,在想不到正解的时候可以考虑分块暴力草()

三、题目

1. Give Away

#include<bits/stdc++.h> using namespace std; /* /\_/\ * (= ._.) * / > \> */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl '\n' #define dbg(x) cout<<#x<<endl;cout<<x<<endl; #define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl; #define YES cout<<"YES"<<endl;return ; #define Yes cout<<"Yes"<<endl;return ; #define NO cout<<"NO"<<endl;return ; #define No cout<<"No"<<endl;return ; typedef long long ll; typedef long double ld; typedef pair<int,int> pii; typedef pair<ll,ll> pll; const int INF=1e9; const ll INFLL=1e18; const int dx[]={-1,1,0,0}; const int dy[]={0,0,-1,1}; const int ddx[]={-2,-1,1,2,2,1,-1,-2}; const int ddy[]={1,2,2,1,-1,-2,-2,-1}; void solve() { int n; cin>>n; vector<int>a(n+1); for(int i=1;i<=n;i++) { cin>>a[i]; } int blen=sqrt(n); int bnum=(n+blen-1)/blen; vector<int>bi(n+1); for(int i=1;i<=n;i++) { bi[i]=(i-1)/blen+1; } vector<int>bl(blen+1); vector<int>br(blen+1); for(int i=1;i<=bnum;i++) { bl[i]=(i-1)*blen+1; br[i]=min(i*blen,n); } vector<int>sorted(n+1); for(int i=1;i<=n;i++) { sorted[i]=a[i]; } for(int i=1;i<=bnum;i++) { sort(sorted.begin()+bl[i],sorted.begin()+br[i]+1); } auto bs=[&](int i,int v)->int { int l=bl[i]; int r=br[i]; int m; int ans=r+1; while(l<=r) { m=l+r>>1; if(sorted[m]>=v) { ans=m; r=m-1; } else { l=m+1; } } return br[i]-ans+1; }; auto query=[&](int l,int r,int v)->int { if(bi[l]==bi[r]) { int ans=0; for(int i=l;i<=r;i++) { if(a[i]>=v) { ans++; } } return ans; } int ans=0; for(int i=l;i<=br[bi[l]];i++) { if(a[i]>=v) { ans++; } } for(int i=bi[l]+1;i<=bi[r]-1;i++) { ans+=bs(i,v); } for(int i=bl[bi[r]];i<=r;i++) { if(a[i]>=v) { ans++; } } return ans; }; auto update=[&](int p,int v)->void { int l=bl[bi[p]]; int r=br[bi[p]]; a[p]=v; for(int i=l;i<=r;i++) { sorted[i]=a[i]; } sort(sorted.begin()+l,sorted.begin()+r+1); }; int m; cin>>m; int op,i,x,l,r,v; while(m--) { cin>>op; if(op==0) { cin>>l>>r>>v; cout<<query(l,r,v)<<endl; } else { cin>>i>>x; update(i,x); } } } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t=1; //cin>>t; init(); while(t--) { solve(); } return 0; }

对于单点修改操作,直接去每个位置修改即可。对于区间查询大于等于 v 的个数操作,考虑每次单点修改后,对这个块排序,那么单点修改的复杂度就是的了。那么在保证每个块内有序后,对于一个区间,中间被完全包含的块就可以暴力枚举,每次通过二分查找大于等于 v 的个数。而对于这个区间左右剩下的散点,因为每个块的大小也是的,所以可以直接去暴力计算大于等于 v 的个数。那么两个操作的复杂度,就都是的了。

实现时,考虑定义 blen 为每个块的长度,bnum 为块数,注意要向上取整。再定义为 i 位置所在的块的编号,和分别为第 i 个块的左右边界。在查询时,注意若区间在同一个块里,那么就直接暴力遍历即可。

2. 教主的魔法

#include <bits/stdc++.h> using namespace std; /* /\_/\ * (= ._.) * / > \> */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl '\n' #define dbg(x) cout<<#x<<endl;cout<<x<<endl; #define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl; #define YES cout<<"YES"<<endl;return ; #define Yes cout<<"Yes"<<endl;return ; #define NO cout<<"NO"<<endl;return ; #define No cout<<"No"<<endl;return ; typedef long long ll; typedef long double ld; typedef pair<int,int> pii; typedef pair<ll,ll> pll; const int INF=1e9; const ll INFLL=1e18; const int dx[]={-1,1,0,0}; const int dy[]={0,0,-1,1}; const int ddx[]={-2,-1,1,2,2,1,-1,-2}; const int ddy[]={1,2,2,1,-1,-2,-2,-1}; void solve() { int n,q; cin>>n>>q; vector<ll>a(n+1); for(int i=1;i<=n;i++) { cin>>a[i]; } int blen=sqrt(n); int bnum=(n+blen-1)/blen; vector<int>bi(n+1); for(int i=1;i<=n;i++) { bi[i]=(i-1)/blen+1; } vector<int>bl(bnum+1); vector<int>br(bnum+1); for(int i=1;i<=bnum;i++) { bl[i]=(i-1)*blen+1; br[i]=min(n,i*blen); } vector<int>sorted(n+1); for(int i=1;i<=n;i++) { sorted[i]=a[i]; } for(int i=1;i<=bnum;i++) { sort(sorted.begin()+bl[i],sorted.begin()+br[i]+1); } vector<ll>lazy(bnum+1); auto bs=[&](int i,int v)->int { int l=bl[i]; int r=br[i]; int m; int ans=r+1; while(l<=r) { m=l+r>>1; if(sorted[m]>=v) { ans=m; r=m-1; } else { l=m+1; } } return br[i]-ans+1; }; auto add=[&](int l,int r,int v)->void { if(bi[l]==bi[r]) { for(int i=l;i<=r;i++) { a[i]+=v; } for(int i=bl[bi[l]];i<=br[bi[l]];i++) { sorted[i]=a[i]; } sort(sorted.begin()+bl[bi[l]],sorted.begin()+br[bi[l]]+1); return ; } for(int i=l;i<=br[bi[l]];i++) { a[i]+=v; } for(int i=bl[bi[l]];i<=br[bi[l]];i++) { sorted[i]=a[i]; } sort(sorted.begin()+bl[bi[l]],sorted.begin()+br[bi[l]]+1); for(int i=bi[l]+1;i<=bi[r]-1;i++) { lazy[i]+=v; } for(int i=bl[bi[r]];i<=r;i++) { a[i]+=v; } for(int i=bl[bi[r]];i<=br[bi[r]];i++) { sorted[i]=a[i]; } sort(sorted.begin()+bl[bi[r]],sorted.begin()+br[bi[r]]+1); }; auto query=[&](int l,int r,int v)->int { if(bi[l]==bi[r]) { int ans=0; for(int i=l;i<=r;i++) { if(a[i]>=v-lazy[bi[l]]) { ans++; } } return ans; } int ans=0; for(int i=l;i<=br[bi[l]];i++) { if(a[i]>=v-lazy[bi[l]]) { ans++; } } for(int i=bi[l]+1;i<=bi[r]-1;i++) { ans+=bs(i,v-lazy[i]); } for(int i=bl[bi[r]];i<=r;i++) { if(a[i]>=v-lazy[bi[r]]) { ans++; } } return ans; }; char op;int l,r,v; while(q--) { cin>>op>>l>>r>>v; if(op=='M') { add(l,r,v); } else { cout<<query(l,r,v)<<endl; } } } void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t=1; //cin>>t; init(); while(t--) { solve(); } return 0; }

和上个题很类似,就是单点修改改成区间增加了。那么和线段树的处理方法类似,设置一个懒信息数组 lazy,每次区间增加时,对于左右的散点暴力,中间的块直接增加懒信息。在区间查询的时候,块内查询大于等于 v-lazy 的个数即可。

3. 蒲公英

#include <bits/stdc++.h> using namespace std; /* /\_/\ * (= ._.) * / > \> */ /* *想好再写 *注意审题 注意特判 *不要红温 不要急躁 耐心一点 *WA了不要立马觉得是思路不对 先耐心找反例 */ #define endl '\n' #define dbg(x) cout<<#x<<" "<<x<<endl; #define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl; #define YES cout<<"YES"<<endl;return ; #define Yes cout<<"Yes"<<endl;return ; #define NO cout<<"NO"<<endl;return ; #define No cout<<"No"<<endl;return ; #define popcount __builtin_popcount using ll=long long; using i128=__int128; using ld=long double; using pii=pair<int,int>; using pll=pair<ll,ll>; const int INF=1e9; const ll INFLL=1e18; const int dx[]={-1,1,0,0}; const int dy[]={0,0,-1,1}; const int ddx[]={-2,-1,1,2,2,1,-1,-2}; const int ddy[]={1,2,2,1,-1,-2,-2,-1}; void solve() { int n,q; cin>>n>>q; vector<int>a(n+1); for(int i=1;i<=n;i++) { cin>>a[i]; } vector<int>sorted=a; sort(sorted.begin()+1,sorted.end()); int len=1; for(int i=2;i<=n;i++) { if(sorted[len]!=sorted[i]) { sorted[++len]=sorted[i]; } } auto rnk=[&](int v)->int { int l=1; int r=len; int m; int ans; while(l<=r) { m=l+r>>1; if(sorted[m]>=v) { ans=m; r=m-1; } else { l=m+1; } } return ans; }; for(int i=1;i<=n;i++) { a[i]=rnk(a[i]); } int blen=sqrt(n); int bnum=(n+blen-1)/blen; vector<int>bi(n+1); for(int i=1;i<=n;i++) { bi[i]=(i-1)/blen+1; } vector<int>bl(bnum+1); vector<int>br(bnum+1); for(int i=1;i<=bnum;i++) { bl[i]=(i-1)*blen+1; br[i]=min(n,i*blen); } vector<vector<int>>freq(bnum+1,vector<int>(len+1)); for(int i=1;i<=bnum;i++) { for(int j=1;j<=len;j++) { freq[i][j]=freq[i-1][j]; } for(int j=bl[i];j<=br[i];j++) { freq[i][a[j]]++; } } vector<vector<int>>mode(bnum+1,vector<int>(bnum+1)); for(int i=1;i<=bnum;i++) { for(int j=i;j<=bnum;j++) { int most=mode[i][j-1]; int mostCnt=freq[j][most]-freq[i-1][most]; for(int k=bl[j];k<=br[j];k++) { int cur=a[k]; int curCnt=freq[j][cur]-freq[i-1][cur]; if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most)) { most=cur; mostCnt=curCnt; } } mode[i][j]=most; } } vector<int>cnts(len+1); int l,r; int pre=0; while(q--) { cin>>l>>r; l=(l+pre-1)%n+1; r=(r+pre-1)%n+1; if(l>r) { swap(l,r); } if(bi[l]==bi[r]) { for(int i=l;i<=r;i++) { cnts[a[i]]++; } int most=0; for(int i=l;i<=r;i++) { if(cnts[a[i]]>cnts[most]||(cnts[a[i]]==cnts[most]&&a[i]<most)) { most=a[i]; } } pre=sorted[most]; cout<<pre<<endl; for(int i=l;i<=r;i++) { cnts[a[i]]=0; } continue; } for(int i=l;i<=br[bi[l]];i++) { cnts[a[i]]++; } for(int i=bl[bi[r]];i<=r;i++) { cnts[a[i]]++; } int most=mode[bi[l]+1][bi[r]-1]; int mostCnt=freq[bi[r]-1][most]-freq[bi[l]][most]; for(int i=l;i<=br[bi[l]];i++) { int cur=a[i]; int curCnt=cnts[cur]+freq[bi[r]-1][cur]-freq[bi[l]][cur]; if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most)) { most=cur; mostCnt=curCnt; } } for(int i=bl[bi[r]];i<=r;i++) { int cur=a[i]; int curCnt=cnts[cur]+freq[bi[r]-1][cur]-freq[bi[l]][cur]; if(curCnt>mostCnt||(curCnt==mostCnt&&cur<most)) { most=cur; mostCnt=curCnt; } } pre=sorted[most]; cout<<pre<<endl; for(int i=l;i<=br[bi[l]];i++) { cnts[a[i]]=0; } for(int i=bl[bi[r]];i<=r;i++) { cnts[a[i]]=0; } } } /* 6 3 1 2 3 2 1 2 1 5 3 6 1 5 1 2 1 */ void init() { } signed main() { ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); int t=1; //cin>>t; init(); while(t--) { solve(); } return 0; }

对于众数这个问题,首先可以考虑维护 freq[i][j] 表示前 i 块里 j 这个数出现的次数,即一个前缀词频。之后,再维护一个 mode[i][j] 表示第 i 块到第 j 块之间的最小众数。那么对于查询,中间的整块就可以使用这两个数组查出众数和词频。然后对于两边的散块,就可以暴力统计词频,判断是否大于中间这个众数即可。

构建时,freq 数组就是正常统计前缀词频即可。对于 mode 数组,就是每次先找出 i 到 j-1 块的众数和词频,然后在遍历当前块的时候,对于块内的每个数判断其区间词频是否可以更新众数即可。

总结

分块,暴力的神!

END

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 10:31:41

操作系统课设六大实验工程化实现与二次开发指南

简介&#xff1a;面向计算机科学专业学生与操作系统课程学习者&#xff0c;这份东北大学操作系统课设资料围绕进程管理、内存管理、文件系统与设备管理等核心模块展开&#xff0c;适合用于课程设计参考、实验复现与原理梳理。压缩包共24个文件&#xff0c;以21个cpp源码为主体&…

作者头像 李华
网站建设 2026/10/29 10:31:13

宽带网络核心网BNC深度解析:转控分离、实施路径与避坑指南

简介&#xff1a;《中国联通宽带网络核心网&#xff08;BNC&#xff09;技术白皮书》由中国联通于2024年7月发布&#xff0c;联合华为、中兴、新华三及诺基亚贝尔等厂商共同编写&#xff0c;面向电信运营商、网络架构师和宽带技术研究人员&#xff0c;系统阐述固定宽带网络向BN…

作者头像 李华
网站建设 2026/10/29 10:25:39

I.MX 6ULL HAB 安全启动实操

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/29 10:21:25

AI编程助手superpowers技能体系:从原理到实操的完整指南

1. 从“superpowers”这个热词说起&#xff1a;它到底是什么第一次看到“superpowers”这个词挂在热搜上的时候&#xff0c;我下意识以为是某部新出的超英电影。点进去才发现&#xff0c;这其实是最近在开发者圈子里被反复讨论的一个概念——一套围绕 AI 编程助手构建的“技能增…

作者头像 李华
网站建设 2026/10/29 10:18:24

给 Crush 配上 LSP:AI 找定义不再靠猜

给 Crush 配上 LSP:AI 找定义不再靠猜 【免费下载链接】crush Glamourous agentic coding for all &#x1f498; 项目地址: https://gitcode.com/gh_mirrors/crush3/crush 刚在 A 文件里改了个变量名&#xff0c;AI 接着改 B 文件时却还在用旧名字猜引用&#xff0c;给…

作者头像 李华
网站建设 2026/10/29 10:17:51

从Java到22K!一位老学员的真实转行故事,普通人也能抓住大模型红利

本文讲述了Java开发小杨从薪资停滞到通过转向大模型应用开发实现薪资翻倍的转行经历。小杨因缺乏项目经验在AI领域受挫&#xff0c;后经评估转向大模型应用开发&#xff0c;通过学习API调用、Prompt工程等项目实践&#xff0c;成功获得22K高薪offer。文章建议传统开发者别死磕底…

作者头像 李华