一道优秀的分块入门题 & 题解:LGP2801 教主的魔法

分块彻底怒了,指出了根本原因,洛谷没有一篇题解把这个排序讲的很清楚适合新手。

题意

一个序列,两种操作:

  • 形如 M l r w,表示将 \([l,r]\) 这个区间的数加上 \(w\)
  • 形如 A l r c,表示询问 \([l,r]\) 区间内有多少个数大于等于 \(c\)

做法

操作类问题,我已经说累了:

优先考虑数据结构,离线下来会不会比在线更好做,实在不行考虑分块大帝。

首先想一想数据结构有没有可以做这道题的,“区间内有多少个数大于等于 \(x\)”,显然的主席树,但是主席数并不支持区间修改(空间复杂度太大),离线呢?因为带修所以离线下来貌似没有什么用,所以分块

考虑直接分,\(\sqrt{n}\) 为一块,发现并不好维护,因为要考虑值域上的询问,那么值域分块?也不可做因为限制固定区间。

此处需要一个小 trick,建议积累:

  • 先将每块提前进行排序,注意要新开一个数组去排,否则会出错。

重点讲几个新手向问题:

  • Q:每块都排不会 T 吗?回:根本不会,每块都排你不考虑均摊都是 \(O(n \log n \sqrt{n})\),均摊下来是 \(O(n \log n)\)
  • Q:你这样都进行排序,后续怎样修改?回:都说了要新开一个数组,并无影响。
  • Q:查询修改怎么办?回:查询时先把边角暴力处理,然后对于每个块中,二分,因为已经实现排好序;修改再重新排。
  • Q:修改重排也会 T!回:关注到块中整体加并不会影响大小关系,只把左端点所在块和右端点所在块重排就行,这样由于只对一个 \(\sqrt{n}\) 去排序,这样单次时间复杂度 \(O(\sqrt{n}\log(\sqrt{n}))\)

CODE

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N],st[N],ed[N],bl[N],s[N],b[N],c[N],tot,maxn;
void add(int l,int r,int x){// 修改
    int ld=bl[l],rd=bl[r];
    if (ld==rd){// 同一块内
        for (int i=l;i<=r;++i){
            a[i]+=x,s[ld]+=x;
        }
        for(int i=st[ld];i<=ed[ld];i++)c[i]=a[i];
        sort(c+st[ld],c+ed[ld]+1);
        return;
    }
    for (int i=l;i<=ed[ld];++i)a[i]+=x,s[ld]+=x;//边角暴力
    for(int i=st[ld];i<=ed[ld];i++)c[i]=a[i];// c 是用来排序的那个数组
    sort(c+st[ld],c+ed[ld]+1);// 排
    for (int i=ld+1;i<rd;++i)b[i]+=x,s[i]+=maxn*x;// s 是当前块和(但是貌似没有维护必要),b 是块内的标记永久化
    for (int i=st[rd];i<=r;++i)a[i]+=x,s[rd]+=x;
    for(int i=st[rd];i<=ed[rd];i++)c[i]=a[i];
    sort(c+st[rd],c+ed[rd]+1);
}
int find(int x,int w){// 块内二分
    int l=st[x],r=ed[x];
    while (l<=r){
        int mid=(l+r)/2;
        if (c[mid]<w)l=mid+1;
        else r=mid-1;
    }
    return ed[x]-l+1;
}
int query(int l,int r,int w){
    int ld=bl[l],rd=bl[r];
    if (ld==rd){
        int cnt=0;
        for (int i=l;i<=r;++i){
            if(a[i]+b[ld]>=w)cnt++;
        }
        return cnt;
    }
    int cnt=0;
    for (int i=l;i<=ed[ld];++i){
        if (a[i]+b[ld]>=w)cnt++;
    }
    for (int i=st[rd];i<=r;++i){
        if (a[i]+b[rd]>=w)cnt++;
    }
    for (int i=ld+1;i<rd;++i){
        cnt+=find(i,w-b[i]);// 一个细节,去掉当前块的标记永久化
    }
    return cnt;
}
int main(){
    int n,q;cin>>n>>q;
    maxn=sqrt(n),tot=(n-1)/maxn+1;
    for (int i=1;i<=n;++i){
        cin>>a[i];c[i]=a[i];
    }
    for (int i=1;i<=tot;++i){// 预处理
        st[i]=(i-1)*maxn+1,ed[i]=min(i*maxn,n);
        for(int j=st[i];j<=ed[i];j++)bl[j]=i;
    }
    for (int i=1;i<=tot;++i)sort(c+st[i],c+ed[i]+1);
    while (q--){
        char op;cin>>op;
        int l,r,w;cin>>l>>r>>w;
        if (op=='A'){
            cout<<query(l,r,w)<<'\n';
        }
        else {
            add(l,r,w);
        }
    }
}

后记

愿天堂没有分块!

posted @ 2025-08-30 17:27  Cefgskol  阅读(5)  评论(0)    收藏  举报