学习笔记——分块

分块

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

预处理

把数列 \(A\) 分成若干长度不超过 \(\lfloor\sqrt{N}\rfloor\) 的段,第 \(i\) 段的左端点是 \((i - 1)\lfloor\sqrt{N}\rfloor +1\),右端点是 \(\min(i\lfloor\sqrt{N}\rfloor,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. 如果 \(l\)\(r\) 同时处于第 \(i\) 段内,则直接将 \(A\left[l\right],A\left[l+1\right],\cdots,A\left[r\right]\) 都加 \(d\) ,同时改变前缀和数组 \(sum\left[i\right] += d * (r-l+1)\)
  2. \(l\)\(p\) 段,\(r\)\(q\) 段。那么对于 \(i \in \left[p + 1,q - 1\right]\),令 \(add\left[i\right]+=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. 如果 \(l\)\(r\) 同时处于第 \(i\) 段内,则答案就是 \(add\left[i\right]*(r - l + 1)+(A\left[l\right]+A\left[l+1\right]+\cdots+A\left[r\right])\)
  2. \(l\)\(p\) 段,\(r\)\(q\) 段,\(ans\)\(0\)。那么对于 \(i \in \left[p + 1,q - 1\right]\),令 \(ans+=sum\left[i\right]+add\left[i\right]*len\left[i\right]\)\(len\left[i\right]\) 表示第 \(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 @ 2024-01-21 10:22  GyrthCurunír  阅读(19)  评论(0编辑  收藏  举报