学习笔记——分块

分块

我们先引入一道题:
有一个长度为 N 的数列 A,然后输入 Q 行指令操作。一种是将数列中 lr 个数都加 d,另一种则是询问数列中 lr 个数的和。
用线段树或树状数组能在 O((N+Q)logN) 的时间内解决,那么用分块呢?

预处理

把数列 A 分成若干长度不超过 N 的段,第 i 段的左端点是 (i1)N+1,右端点是 min(iN,N)
然后算出每段的前缀和数组 sum ,将每段的增量标记 add 赋值为 0

tmp=sqrt(n);
for(int i=1;i<=tmp;i++){
    L[i]=(i-1)*sqrt(n)+1;
    R[i]=i*sqrt(n);
}
if(R[tmp]<n)tmp++,L[tmp]=R[tmp-1]+1,R[tmp]=n;
for(int i=1;i<=tmp;i++){
    for(int j=L[i];j<=R[i];j++){
        pos[j]=i;
        sum[i]+=a[j];
    }
}

区间加法

  1. 如果 lr 同时处于第 i 段内,则直接将 A[l],A[l+1],,A[r] 都加 d ,同时改变前缀和数组 sum[i]+=d(rl+1)
  2. lp 段,rq 段。那么对于 i[p+1,q1],令 add[i]+=d。而对于不足一整段的开头和结尾两部分则按照第 1 种情况更新。
void change(int l,int r,int d){
    int p=pos[l],q=pos[r];//pos数组存储每个位置是哪一段
    if(p==q){
        for(int i=l;i<=r;i++)a[i]+=d;
        sum[p]+=d*(r-l+1);
    }else{
        for(int i=p+1;i<=q-1;i++)add[i]+=d;
        for(int i=l;i<=R[p];i++)a[i]+=d;
        sum[p]+=d*(R[p]-l+1);
        for(int i=L[q];i<=r;i++)a[i]+=d;
        sum[q]+=d*(r-L[q]+1);
    }
}

区间求和

  1. 如果 lr 同时处于第 i 段内,则答案就是 add[i](rl+1)+(A[l]+A[l+1]++A[r])
  2. lp 段,rq 段,ans0。那么对于 i[p+1,q1],令 ans+=sum[i]+add[i]len[i]len[i] 表示第 i 段长度。而对于不足一整段的开头和结尾两部分则按照第 1 种情况计算。
int ask(int l,int r){
    int p=pos[l],q=pos[r];
    int ans=0;
    if(p==q){
        for(int i=l;i<=r;i++)ans+=a[i];
        ans+=add[p]*(r-l+1);
    }else{
        for(int i=p+1;i<=q-1;i++)
            ans+=sum[i]+add[i]*(R[i]-L[i]+1);
        for(int i=l;i<=R[p];i++)ans+=a[i];
        ans+=add[p]*(R[p]-l+1);
        for(int i=L[q];i<=r;i++)ans+=a[i];
        ans+=add[q]*(r-L[q]+1);
    }
    return ans;
}

Example

这里以Luogu P2801为例。
分块好题,只不过预处理时需要排序。

#include<bits/stdc++.h>
#define int long long
#define MAX 1000005
using namespace std;
int n,q,a[MAX],b[MAX],tmp,L[MAX],R[MAX],add[MAX],sum[MAX],pos[MAX];
char ch;
void change(int l,int r,int w){
    int p=pos[l],q=pos[r];
    if(p==q){
        for(int i=l;i<=r;i++)a[i]+=w;
        for(int i=L[p];i<=R[q];i++)b[i]=a[i];
        sort(b+L[p],b+R[q]+1);
    }else{
        for(int i=p+1;i<=q-1;i++)add[i]+=w;
        for(int i=l;i<=R[p];i++)a[i]+=w;
        for(int i=L[p];i<=R[p];i++)b[i]=a[i];
        sort(b+L[p],b+R[p]+1);
        for(int i=L[q];i<=r;i++)a[i]+=w;
        for(int i=L[q];i<=R[q];i++)b[i]=a[i];
        sort(b+L[q],b+R[q]+1);
    }
}
int ask(int l,int r,int w){
    int p=pos[l],q=pos[r];
    int ans=0,t;
    if(p==q){
        for(int i=l;i<=r;i++){
            if(add[p]+a[i]>=w)ans++;
        }
    }else{
        for(int i=p+1;i<=q-1;i++){
            t=lower_bound(b+L[i],b+R[i]+1,w-add[i])-b;
            ans+=R[i]-t+1;
        }
        for(int i=l;i<=R[p];i++){
            if(add[p]+a[i]>=w)ans++;
        }
        for(int i=L[q];i<=r;i++){
            if(add[q]+a[i]>=w)ans++;
        }
    }
    return ans;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
    tmp=sqrt(n);
    for(int i=1;i<=tmp;i++){
        L[i]=(i-1)*sqrt(n)+1;
        R[i]=i*sqrt(n);
    }
    if(R[tmp]<n)tmp++,L[tmp]=R[tmp-1]+1,R[tmp]=n;
    for(int i=1;i<=tmp;i++){
        for(int j=L[i];j<=R[i];j++){
            pos[j]=i;
            sum[i]+=a[j];
        }
    }
    for(int i=1;i<=tmp;i++){
        sort(b+L[i],b+R[i]+1);
    }
    int l,r,w;
    while(q--){
        cin>>ch>>l>>r>>w;
        if(ch=='M')change(l,r,w);
        else cout<<ask(l,r,w)<<'\n';
    }
    return 0;
}
posted @   Adventurer_XIV  阅读(26)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示