分块
其实分块也没学多少。
谈谈一开始对这个算法的理解吧。
分块算法主要是对应区间操作与区间 / 单点查询。所谓分块就是将 有 n 个元素的数组分成 n / m 块, 一般 m 取 sqrt(n)。
一般操作的区间会被划分成三块, [L, L所在块的右界] (不完整的块), 很多个被划分的块(完整的块),[R所在块的左界,R](不完整的块)
对于不完整的块是进行暴力操作,对于完整的块是对其打上需要的标记,到查询的时候再将标记操作上。
一般来说这样会使得原本时间复杂度为O(n)的操作降到O(sqrt(n)),从而达到题目要求。
分块一:传送门
简要题意:对于一个长度为n的数列进行区间加法修改和单点询问。

#include <bits/stdc++.h> using namespace std; #define mod 998244353 #define pi acos(-1) #define inf 0x7fffffff #define ll long long 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; } int n, blo_sz; int val[50005], bl_num[50005], atag[50005]; void add(int a, int b, int c) { for(int i=a;i<=min(bl_num[a]*blo_sz,b);i++) //将a到min(a所在块的right, b)暴力加上c val[i]+=c; if(bl_num[a]!=bl_num[b]) for(int i=(bl_num[b]-1)*blo_sz+1;i<=b;i++) //将(b所在块的left, b)暴力加上c val[i]+=c; for(int i=bl_num[a]+1;i<=bl_num[b]-1;i++) //给中间部分的块打上标记 atag[i]+=c; } int main() { n=read(); blo_sz = sqrt(n); for(int i=1;i<=n;i++) val[i]=read(); for(int i=1;i<=n;i++)bl_num[i]=(i-1)/blo_sz+1; for(int i=1;i<=n;i++) { int f=read(),a=read(),b=read(),c=read(); if(f==0)add(a,b,c); if(f==1)printf("%d\n",val[b] + atag[bl_num[b]]); } return 0; }
分块二:传送门
简要题意:给出一个长为 n的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x的元素个数
这道题主要是维护方式改变了。维护的是一个有序的块,同时发现了他的修改是在原数组上直接暴力修改的。只有块才会打上标记。
(一开始觉得可以用multiset的这样就不用sort了,之后发现set好像是不能很快统计一个数之前有多少个数的,还是vector来的快)

#include <bits/stdc++.h> using namespace std; #define mod 998244353 #define pi acos(-1) #define inf 0x7fffffff #define ll long long 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; } int n,blo; int v[50005],bl[50005],atag[50005]; vector<int>ve[505]; void reset(int x) { ve[x].clear(); for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++) ve[x].push_back(v[i]); //这里的v[i]代表的是原始数组的值,而不是分块之后的块 sort(ve[x].begin(),ve[x].end()); } //更新也是分三块 void add(int a,int b,int c) { for(int i=a;i<=min(bl[a]*blo,b);i++) v[i]+=c; //这里也是在原数组上修改,而不是在块内 //突然发现原来的也是在原数组的基础上修改 reset(bl[a]); //所以每次修改完之后都需要update块内元素 if(bl[a]!=bl[b]) { for(int i=(bl[b]-1)*blo+1;i<=b;i++) v[i]+=c; reset(bl[b]); } for(int i=bl[a]+1;i<=bl[b]-1;i++) atag[i]+=c; } //查询也是分三块 int query(int a,int b,int c) { int ans=0; for(int i=a;i<=min(bl[a]*blo,b);i++) if(v[i]+atag[bl[a]]<c)ans++; if(bl[a]!=bl[b]) for(int i=(bl[b]-1)*blo+1;i<=b;i++) if(v[i]+atag[bl[b]]<c)ans++; for(int i=bl[a]+1;i<=bl[b]-1;i++) { int x=c-atag[i]; ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin(); } return ans; } int main() { n=read();blo=sqrt(n); for(int i=1;i<=n;i++)v[i]=read(); for(int i=1;i<=n;i++) { bl[i]=(i-1)/blo+1; ve[bl[i]].push_back(v[i]); } for(int i=1;i<=bl[n];i++) sort(ve[i].begin(),ve[i].end()); for(int i=1;i<=n;i++) { int f=read(),a=read(),b=read(),c=read(); if(f==0)add(a,b,c); if(f==1)printf("%d\n",query(a,b,c*c)); } return 0; }
分块三:传送门
简要题意:给出一个长为 n 的数列,以及 n 个操作,操作涉及区间加法,询问区间内小于某个值 x 的前驱(比其小的最大元素)
这道题就不需要统计个数了,所以就可以用set去维护。但是记得query的时候

#include <bits/stdc++.h> #define mod 998244353 #define pi acos(-1) #define inf 0x7fffffff #define ll long long using namespace std; 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; } int n,blo; int val[100005],bl_nm[100005],atag[100005]; set<int> st[105]; void add(int a,int b,int c) { for(int i=a;i<=min(bl_nm[a]*blo,b);i++) { st[bl_nm[a]].erase(val[i]); val[i]+=c; st[bl_nm[a]].insert(val[i]); } if(bl_nm[a]!=bl_nm[b]) { for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++) { st[bl_nm[b]].erase(val[i]); val[i]+=c; st[bl_nm[b]].insert(val[i]); } } for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++) atag[i]+=c; } int query(int a,int b,int c) { int ans=-1; //取左块最大 for(int i=a;i<=min(bl_nm[a]*blo,b);i++) { int temp=val[i]+atag[bl_nm[a]]; if(temp<c)ans=max(temp,ans); } //取右块最大 if(bl_nm[a]!=bl_nm[b]) for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++) { int temp=val[i]+atag[bl_nm[b]]; if(temp<c)ans=max(temp,ans); } //取中间所有块数的最大 for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++) { int x=c-atag[i]; //这里是为了不对块内元素进行变化,使得常数变大。 set<int>::iterator it=st[i].lower_bound(x); if(it==st[i].begin())continue; //防止全部都大于 --it; ans=max(ans,*it+atag[i]); } return ans; } int main() { n=read();blo=1000; for(int i=1;i<=n;i++)val[i]=read(); for(int i=1;i<=n;i++) { bl_nm[i]=(i-1)/blo+1; st[bl_nm[i]].insert(val[i]); } for(int i=1;i<=n;i++) { int f=read(),a=read(),b=read(),c=read(); if(f==0)add(a,b,c); if(f==1)printf("%d\n",query(a,b,c)); } return 0; }
分块四:传送门
简要题意: 给出一个长为 n 的数列,以及 n个操作,操作涉及区间加法,区间求和。
相较于分块一,只不过多了一个维护的数组sum,其所代表的意义是块的和。那么左右块直接暴力加,之后加上中间块的sum[i]+block_size*tag[block]就行

#include <bits/stdc++.h> #define mod 998244353 #define pi acos(-1) #define inf 0x7fffffff #define ll long long using namespace std; 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; } int n,blo; int bl_nm[50005]; ll val[50005],atag[50005],sum[50005]; void add(int a,int b,int c) { for(int i=a;i<=min(bl_nm[a]*blo,b);i++) val[i]+=c,sum[bl_nm[a]]+=c;; if(bl_nm[a]!=bl_nm[b]) for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++) val[i]+=c,sum[bl_nm[b]]+=c; for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++) atag[i]+=c; } ll query(int a,int b) { ll ans=0; for(int i=a;i<=min(bl_nm[a]*blo,b);i++) ans+=val[i]+atag[bl_nm[a]]; if(bl_nm[a]!=bl_nm[b]) for(int i=(bl_nm[b]-1)*blo+1;i<=b;i++) ans+=val[i]+atag[bl_nm[b]]; for(int i=bl_nm[a]+1;i<=bl_nm[b]-1;i++) ans+=sum[i]+blo*atag[i]; return ans; } int main() { n=read();blo=sqrt(n); for(int i=1;i<=n;i++)val[i]=read(); for(int i=1;i<=n;i++) { bl_nm[i]=(i-1)/blo+1; sum[bl_nm[i]]+=val[i]; } for(int i=1;i<=n;i++) { int f=read(),a=read(),b=read(),c=read(); if(f==0)add(a,b,c); if(f==1) printf("%d\n",query(a,b)%(c+1)); } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人