[知识学习] 分块
贴几个板子,供自己看
分块核心:暴力操作
把一串序列分成√n个块,对每个块操作
左端不完整块,右端不完整块暴力计算
中间完整块lazy标记
最简单的分块题:
#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];//a原数组,lazy懒标记,pos[i]:i对应的块,L[i]:块i的左端点,R[i]:块i的右端点
void add(int l,int r,int c){//区间加
for(int i=l;i<=min(R[pos[l]],r);i++){//左端不完整区间处理
a[i]+=c;
}
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){//右端不完整区间处理
a[i]+=c;
}
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;//中间完整区间处理
}
int main(){
scanf("%d",&n);
k=sqrt(n);//每k个数分1块
int sum=n/k+(n%k!=0);//共分sum个块
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1;//每个数在哪个块
for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;//左右端点
R[sum]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%d\n",a[r]+lazy[pos[r]]);
}
return 0;
}
支持区间加,查找区间内前驱
可以利用C++STL里的set维护每个块,查lower_bound
每次修改时先把块里的删掉,修改后在加进来
#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];
set<int> t[N];
void add(int l,int r,int c){//同上
for(int i=l;i<=min(R[pos[l]],r);i++){
t[pos[l]].erase(a[i]);//先删掉原始的
a[i]+=c;//更改
t[pos[l]].insert(a[i]);//再插入新的
}
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){
t[pos[r]].erase(a[i]);
a[i]+=c;
t[pos[r]].insert(a[i]);
}
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;
}
int pre(int l,int r,int c){//查询前驱,用set维护
int ans=-1;
for(int i=l;i<=min(R[pos[l]],r);i++)//统计左端不完整区间
if(a[i]+lazy[pos[l]]<c) ans=max(ans,a[i]+lazy[pos[l]]);
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++)//统计右端不完整区间
if(a[i]+lazy[pos[r]]<c) ans=max(ans,a[i]+lazy[pos[r]]);
}
for(int i=pos[l]+1;i<pos[r];i++){//用STL找中间的最大值
int k=c-lazy[i];
set<int>::iterator it=t[i].lower_bound(k);
if(it==t[i].begin()) continue;
it--;
ans=max(ans,*it+lazy[i]);
}
return ans;
}
int main(){
scanf("%d",&n);
k=sqrt(n);
int sum=n/k+(n%k!=0);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1,t[pos[i]].insert(a[i]);
for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;
R[sum]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%d\n",pre(l,r,c));
}
return 0;
}
支持区间加,区间求和
思路大同小异,这里不细说,依然是老套路
#include <bits/stdc++.h>
#define N (100000+5)
#define int long long//开long long
using namespace std;
int n,k,b,a[N],pos[N],lazy[N],sum[N],L[N],R[N];
void add(int l,int r,int c){//区间加操作,不解释了
for(int i=l;i<=min(R[pos[l]],r);i++) a[i]+=c,sum[pos[l]]+=c;
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++) a[i]+=c,sum[pos[r]]+=c;
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c,sum[i]+=c*k;
}
int getsum(int l,int r,int c){//区间求和操作,一样的道理
int ans=0;
for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]+lazy[pos[l]],ans%=(c+1);
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++) ans+=a[i]+lazy[pos[r]],ans%=(c+1);
}
for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i],ans%=(c+1);
return ans%(c+1);
}
signed main(){
scanf("%lld",&n);
k=sqrt(n);
b=(n/k)+(n%k!=0);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[i]=(i-1)/k+1;
sum[pos[i]]+=a[i];
}
for(int i=1;i<=b;i++){
L[i]=(i-1)*k+1,R[i]=i*k;
}
R[b]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%lld\n",getsum(l,r,c));
}
return 0;
}
支持区间开方,区间求和
开方:最终得到的数都会是0或1(向下取整了)
所以分块维护,当哪个块里的数全部都是0或1是,再怎么开方都不会变了
所以用vis数组记录一下那些块已经只有0或1,就可以大大节约复杂度
#include <bits/stdc++.h>
#define N (1000000+5)
#define int long long
using namespace std;
int n,bl,k,a[N],sum[N],pos[N],L[N],R[N];
bool vis[N];
void solve(int x){//解决完整块开方的函数
if(vis[x]) return;//这个块全部都是0或1,直接跳出不用开方
vis[x]=1;
for(int i=L[x];i<=R[x];i++){
sum[x]-=a[i];//减去
a[i]=sqrt(a[i]);//开方
sum[x]+=a[i];//再加上
if(a[i]>1) vis[x]=0;
}
}
void add(int l,int r,int c){//不完整块开方函数,同理
if(!vis[pos[l]]){
for(int i=l;i<=min(R[pos[l]],r);i++){
sum[pos[l]]-=a[i];
a[i]=sqrt(a[i]);
sum[pos[l]]+=a[i];
}
vis[pos[l]]=1;
for(int i=L[pos[l]];i<=R[pos[l]];i++){
if(a[i]>1){
vis[pos[l]]=0;
break;
}
}
}
if(!vis[pos[r]]&&pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){
sum[pos[r]]-=a[i];
a[i]=sqrt(a[i]);
sum[pos[r]]+=a[i];
}
vis[pos[r]]=1;
for(int i=L[pos[r]];i<=R[pos[r]];i++){
if(a[i]>1){
vis[pos[r]]=0;
break;
}
}
}
for(int i=pos[l]+1;i<pos[r];i++) solve(i);
}
int getsum(int l,int r,int c){
int ans=0;
for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i];
if(pos[l]!=pos[r])for(int i=L[pos[r]];i<=r;i++) ans+=a[i];
for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i];
return ans;
}
signed main(){
scanf("%lld",&n);
k=sqrt(n),bl=(n/k)+(n%k!=0);//同样
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[i]=(i-1)/k+1;
sum[pos[i]]+=a[i];
}
for(int i=1;i<=bl;i++){
L[i]=(i-1)*k+1;
R[i]=i*k;
}
R[bl]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%lld\n",getsum(l,r,c));
}
return 0;
}
就到这里吧
转载请注明出处--Xx_queue
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· C# 13 中的新增功能实操
· Ollama本地部署大模型总结
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(4)
· 卧槽!C 语言宏定义原来可以玩出这些花样?高手必看!
· langchain0.3教程:从0到1打造一个智能聊天机器人