• LOJ——#6277. 数列分块入门 1


    ~~推荐播客~~

    「分块」数列分块入门1 – 9 by hzwer

    浅谈基础根号算法——分块

    博主蒟蒻,有缘人可直接观摩以上大佬的博客。。。

    #6277. 数列分块入门 1

    题目大意:

    给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,单点查值。

    分块入门,区间修改+单点查询

    所谓分块,实际上是一种优美的暴力,算是一种数据结构吧。。。
    将区间分成许多大小相同的块,对于多出来的部分暴力去做,一般块的大小为$√n$

    看了大佬们的代码,有点儿懵逼=_=
    来模拟一下流程吧,可能会清晰一点儿

     

    如图所示,暴力地将一段长度为$10$的序列分成了$4$块,其中$3$块是大小为$√n$的块。
    每个数所属块的标号显然是$bl[i]=(i-1)/blo+1$,其中$blo$为块的大小(手推一下即可)
    假如你暴力修改某个区间,假设为$[2,7]$,(算了,自己暴力模拟吧
    你首先要做的是暴力修改小区间,此时小区间为$[2.3]$和$[7,7]$

    这里小区间一般有两个,即最左边不在整个块中的数,和最右边不在整个块中的数

    手推一下,暴力修改的左边区间为$[l,min(bl[l]*blo],r)$
    emmmmm,我知道暴力修改左边区间的右端点就是$bl[l]*blo]$,为什么还要和给定修改的区间右端点去$min$呢?
    。。。思考一下?(大佬说显然嘛
    有可能这个待修改区间$[l,r]$就在一个块中。

    暴力修改的右区间左短点为$(bl[r]-1)*blo+1$
    在这之前要特判一下$bl[l]==bl[r]$,也是防止待修改区间$[l,r]$在一个块中的情况

    最后,当然是对完整的块的修改,范围是$bl[l+1],bl[r]-1$,这时修改的是区间加法标记

     
    #include<bits/stdc++.h>
    
    #define N 1010100
    using namespace std;
    
    int n,a[N],blo[N],atag[N];
    
    void update(int l,int r,int c){
        for(int i=l;i<=min(blo[l]*blo[0],r);i++)
            a[i]+=c;
        if(blo[l]!=blo[r])
            for(int i=(blo[r]-1)*blo[0]+1;i<=r;i++)
                a[i]+=c;
        for(int i=blo[l]+1;i<=blo[r]-1;i++)
            atag[i]+=c;
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        blo[0]=sqrt(n);
        for(int i=1;i<=n;i++) blo[i]=(i-1)/blo[0]+1;//每一个数所在的块
        for(int i=1;i<=n;i++){
            int opt,l,r,c;
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(!opt) update(l,r,c);
            else printf("%d
    ",a[r]+atag[blo[r]]);
        }return 0;
    }

    #6278. 数列分块入门 2

    给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的元素个数。

    跟随大佬的思路,既然一个题你要考虑用分块去做,那么你要考虑以下$3$项

    1.不完整的块如何处理?

    2.整块如何处理?

    3.预处理什么信息?

    此题关键在于查找区间内小于$x$的元素个数,倘若这个区间不是有序的,难道要暴力查询吗?(暴力只能针对于不完整的块)

    那么就必须让他完整的块变得有序,然后二分查找这个快里$<x$的数的个数即可

    修改完成之后,边上的那两个块不就部分改变了吗,怎么维护呢?

    当然还是暴力,暴力清除块,在暴力添加回去。

    $vector$大法好

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<vector>
    #include<algorithm>
    
    #define N 500056
    using namespace std;
    
    int bl[N],n,v[N],atag[N],blo;
    vector<int>V[N];
    
    void update(int x){//第x个块 
        V[x].clear();
        for(int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
            V[x].push_back(v[i]);
        sort(V[x].begin(),V[x].end());
    }
    
    void add(int l,int r,int val){
        for(int i=l;i<=min(bl[l]*blo,r);i++)
            v[i]+=val;
        update(bl[l]);
        if(bl[l]!=bl[r]){
            for(int i=(bl[r]-1)*blo+1;i<=r;i++)
                v[i]+=val;
            update(bl[r]);
        }
        for(int i=bl[l]+1;i<=bl[r]-1;i++)
            atag[i]+=val;
    }
    
    int query(int l,int r,int c){
        int ans=0;
        for(int i=l;i<=min(bl[l]*blo,r);i++)
            if(v[i]+atag[bl[l]]<c) ++ans;
        if(bl[l]!=bl[r]){
            for(int i=(bl[r]-1)*blo+1;i<=r;i++)
                if(v[i]+atag[bl[r]]<c) ++ans;
        }
        for(int i=bl[l]+1;i<=bl[r]-1;i++){
            int x=c-atag[i];
            ans+=lower_bound(V[i].begin(),V[i].end(),x)-V[i].begin();
        }
        return ans;
    }
    
    int main()
    {
        scanf("%d",&n);
        blo=sqrt(n);
        for(int i=1;i<=n;i++){
            bl[i]=(i-1)/blo+1;//是除以这个块的大小 
            scanf("%d",&v[i]);
            V[bl[i]].push_back(v[i]);
        }
        for(int i=1;i<=bl[n];i++) 
            sort(V[i].begin(),V[i].end());
        for(int opt,l,r,c,i=1;i<=n;i++){
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(!opt) add(l,r,c);
            else printf("%d
    ",query(l,r,c*c));
        }
        
        return 0;
    }

    #6279. 数列分块入门 3

    我只能说这些骚操作我从来没见过,仿佛打开了异世界的大门,(大佬们早就进去看了不知多少遍了

    题目大意:

    给出一个长为 $n$的数列,以及 $n$ 个操作,操作涉及区间加法,询问区间内小于某个值 $x$ 的前驱(比其小的最大元素)。

    上一道题是$vector$二分查找,反之就是$STL$,而这一道也是这样的,不过用的是$set$

    hzwer(他想真实表达的东西):

    可以在块内维护其它结构使其更具有拓展性,比如放一个 $set$ ,这样如果还有插入、删除元素的操作,会更加的方便。

    即分块套其他数据结构,%%%%强无敌呀!!

    分块的调试检测技巧:

    可以生成一些大数据,然后用两份分块大小不同的代码来对拍,还可以根据运行时间尝试调整分块大小,减小常数。——hzwer

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<algorithm>
    
    #define N 500056
    using namespace std;
    
    int bl[N],n,v[N],atag[N],blo;
    set<int>S[N];
    
    void add(int l,int r,int val){
        for(int i=l;i<=min(bl[l]*blo,r);i++){
            S[bl[l]].erase(v[i]);
            v[i]+=val;
            S[bl[l]].insert(v[i]);
        } 
        if(bl[l]!=bl[r])
            for(int i=(bl[r]-1)*blo+1;i<=r;i++){
                S[bl[r]].erase(v[i]);
                v[i]+=val;
                S[bl[r]].insert(v[i]);
            }
        for(int i=bl[l]+1;i<=bl[r]-1;i++)
            atag[i]+=val;
    }
    
    int query(int l,int r,int val){
        int ans=-1;
        for(int i=l;i<=min(bl[l]*blo,r);i++)
            if(v[i]+atag[bl[l]]<val) ans=max(ans,v[i]+atag[bl[l]]);
        if(bl[l]!=bl[r]){
            for(int i=(bl[r]-1)*blo+1;i<=r;i++)
                if(v[i]+atag[bl[r]]<val) ans=max(ans,v[i]+atag[bl[r]]);
        }
        for(int i=bl[l]+1;i<=bl[r]-1;i++){
            int x=val-atag[i];
            set<int>::iterator it=S[i].lower_bound(x);//二分查找
            if(it==S[i].begin()) continue;
            --it;
            ans=max(ans,*it+atag[i]);
        }
        return ans;
    }
    
    int main()
    {
        scanf("%d",&n);
        blo=sqrt(n);
        for(int i=1;i<=n;i++){
            bl[i]=(i-1)/blo+1;//是除以这个块的大小 
            scanf("%d",&v[i]);
            S[bl[i]].insert(v[i]);
        }
        for(int opt,l,r,c,i=1;i<=n;i++){
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(!opt) add(l,r,c);
            else printf("%d
    ",query(l,r,c));
        }
        return 0;
    }

    #6280. 数列分块入门 4

    给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及区间加法,区间求和。

    现在看着道题,你会说,这不就是线段树裸题吗,水水水水!

    当你做了分块前$3$道题目后,看到这道题,你也会说,这不是分块裸题吗,随便做。

    %%%%%(为什么我都说不出来呢?

    交了5遍才A掉它,太菜啦,太菜啦。。。

    有两个细节,放在代码里了。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<vector>
    #include<set>
    #include<algorithm>
    
    #define N 500056
    #define LL long long
    using namespace std;
    
    LL bl[N],n,v[N],atag[N],blo,sum[N];
    
    void add(LL l,LL r,LL val){
        for(LL i=l;i<=min(bl[l]*blo,r);i++)
            v[i]+=val,sum[bl[l]]+=val;//修改不完整的块时那个块的区间总和也随之改变 
        if(bl[l]!=bl[r])
            for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
                v[i]+=val,sum[bl[r]]+=val;
        for(LL i=bl[l]+1;i<=bl[r]-1;i++)
            atag[i]+=val; 
    }
    LL query(LL l,LL r,LL mod){
        LL ans=0;
        for(LL i=l;i<=min(bl[l]*blo,r);i++)
            ans=(ans%mod+(v[i]+atag[bl[l]])%mod)%mod;
        if(bl[l]!=bl[r])
            for(LL i=(bl[r]-1)*blo+1;i<=r;i++)
                ans=(ans%mod+(v[i]+atag[bl[r]])%mod)%mod;
        for(LL i=bl[l]+1;i<=bl[r]-1;i++)
            ans=(ans%mod+(sum[i]+atag[i]*blo)%mod)%mod;//标记不要误写成bl[i] 
        return ans%mod;
    }
    
    int main()
    {
        scanf("%lld",&n);
        blo=sqrt(n);
        for(LL i=1;i<=n;i++){
            bl[i]=(i-1)/blo+1;//是除以这个块的大小 
            scanf("%lld",&v[i]);
            sum[bl[i]]+=v[i];
        }
        for(LL opt,l,r,c,i=1;i<=n;i++){
            scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
            if(!opt) add(l,r,c);
            else printf("%lld
    ",query(l,r,c+1));
        }
        return 0;
    }

    #6281. 数列分块入门 5

    给出一个长为 $n$ 的数列 $a_1ldots a_n$ ,以及 $n$ 个操作,操作涉及区间开方,区间求和。

    区间开方?操作很骚啊。。。(就是不会

    不难发现,这题的修改就只有下取整开方,而一个数经过几次开方之后,它的值就会变成 0 或者 1。——hzwer

    然后就可以随便搞啦。。。。(详情见代码

    #include<bits/stdc++.h>
    
    #define N 5000000
    using namespace std;
    
    int n,bl[N],v[N],sum[N],blo;
    bool flg[N];
    
    void change_x(int x){
        if(flg[x]) return;
        flg[x]=1;
        sum[x]=0;
        for(int i=(x-1)*blo+1;i<=x*blo;i++){
            v[i]=sqrt(v[i]);
            if(v[i]>1) flg[x]=0;
            sum[x]+=v[i];
        }
    }
    
    void change(int l,int r){
        for(int i=l;i<=min(bl[l]*blo,r);i++)
            sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
        if(bl[l]!=bl[r]){
            for(int i=(bl[r]-1)*blo+1;i<=min(r,n);i++){
                sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
            }
        }
        for(int i=bl[l]+1;i<=bl[r]-1;i++)
            change_x(i);
    }
    int query(int l,int r){
        int ans=0;
        for(int i=l;i<=min(bl[l]*blo,r);i++)
            ans+=v[i];
        if(bl[l]!=bl[r]){
            for(int i=(bl[r]-1)*blo+1;i<=r;i++)
                ans+=v[i];
        }
        for(int i=bl[l]+1;i<=bl[r]-1;i++)
            ans+=sum[i];
        return ans;
    }
    
    int main()
    {
        scanf("%d",&n);
        blo=sqrt(n);
        for(int i=1;i<=n;i++){
            scanf("%d",&v[i]);
            bl[i]=(i-1)/blo+1;
            sum[bl[i]]+=v[i];
        }
        for(int opt,l,r,c,i=1;i<=n;i++){
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(!opt) change(l,r);
            else printf("%d
    ",query(l,r));
        }
        
        return 0;
    }

    P4145 上帝造题的七分钟2 / 花神游历各国

    有点儿坑的地方是$l$有可能大于$r$,所以要$swap(l,r)$

    #include<bits/stdc++.h>
    
    #define LL long long
    #define N 1000000
    using namespace std;
    
    LL n,m,v[N],sum[N],bl[N],blo;
    bool flg[N];
    
    void change_sqrt(LL x) {
        if(flg[x]) return;
        flg[x]=1;
        sum[x]=0;
        for(LL i=(x-1)*blo+1; i<=x*blo; i++) {
            v[i]=sqrt(v[i]),sum[x]+=v[i];
            if(v[i]>1) flg[x]=0;
        }
    }
    
    void add(LL l, LL r) {
        if(l>r) swap(l,r);
        for(LL i=l; i<=min(bl[l]*blo,r); i++)
            sum[bl[l]]-=v[i],v[i]=sqrt(v[i]),sum[bl[l]]+=v[i];
        if(bl[l]!=bl[r]) {
            for(LL i=(bl[r]-1)*blo+1; i<=r; i++) {
                sum[bl[r]]-=v[i],v[i]=sqrt(v[i]),sum[bl[r]]+=v[i];
            }
        }
        for(LL i=bl[l]+1; i<=bl[r]-1; i++)
            change_sqrt(i);
    }
    
    inline LL query(LL l,LL r) {
        if(l>r) swap(l,r);
        LL ans=0;
        for(LL i=l; i<=min(bl[l]*blo,r); i++)
            ans+=v[i];
        if(bl[l]!=bl[r]) {
            for(LL i=(bl[r]-1)*blo+1; i<=r; i++)
                ans+=v[i];
        }
        for(LL i=bl[l]+1; i<=bl[r]-1; i++)
            ans+=sum[i];
        return ans;
    }
    
    int main() {
        scanf("%lld",&n);
        blo=sqrt(n);
        for(LL i=1; i<=n; i++) {
            scanf("%lld",&v[i]);
            bl[i]=(i-1)/blo+1;
            sum[bl[i]]+=v[i];
        }
        scanf("%lld",&m);
        for(LL opt,l,r,i=1; i<=m; i++) {
            scanf("%lld%lld%lld",&opt,&l,&r);
            if(!opt) add(l,r);
            else printf("%lld
    ",query(l,r));
        }
    
        return 0;
    }
    分块

    #6282. 数列分块入门 6

    给出一个长为 $n$ 的数列,以及 $n$ 个操作,操作涉及单点插入,单点询问,数据随机生成。

    暴力插入,块重构,当元一个块内素数量很多时,重新构造

    $vector && pair$

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    
    #define N 200005
    using namespace std;
    
    int n,v[N],st[N],blo,m;
    vector<int>ve[1005];
    
    pair<int,int>query(int x){
        int t=1;
        while(ve[t].size()<x){
            x-=ve[t].size();++t;
        }
        return make_pair(t,x-1);
    }
    
    void rebuild(){
        int top=0;
        for(int i=1;i<=m;i++){
            for(vector<int>::iterator j=ve[i].begin();j!=ve[i].end();j++)
                st[++top]=*j;
            ve[i].clear();
        }
        int blo2=sqrt(top);
        for(int i=1;i<=top;i++)
            ve[(i-1)/blo2+1].push_back(st[i]);
        m=(top-1)/blo2+1;
    }
    
    void insert(int a,int b){
        pair<int,int> t=query(a);
        ve[t.first].insert(ve[t.first].begin()+t.second,b);
        if(ve[t.first].size()>20*blo)
            rebuild();
    }
    
    int main()
    {
        scanf("%d",&n);blo=sqrt(n);
        for(int i=1;i<=n;i++){
            scanf("%d",&v[i]);
            ve[(i-1)/blo+1].push_back(v[i]);
        }
        m=(n-1)/blo+1;//统计一共有多少块
        for(int opt,l,r,c,i=1;i<=n;i++){
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(!opt) insert(l,r);
            else {
                pair<int,int>t=query(r);
                printf("%d
    ",ve[t.first][t.second]);
            }
        }
        
        return 0;
    }

    #6283. 数列分块入门 7

    区间乘法+区间加法+单点查询

    修改时,对边上的块直接下放标记,乘先加后

    #include<bits/stdc++.h>
    
    #define N 200005
    #define mod 10007
    using namespace std;
    
    int n,v[N],mtag[N],atag[N],bl[N],blo;
    
    void reserve(int x) {
        for(int i=(x-1)*blo+1; i<=min(x*blo,n); i++)
            v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
        atag[x]=0,mtag[x]=1;
    }
    
    void add(int f,int l,int r,int c) {
        reserve(bl[l]);
        for(int i=l; i<=min(bl[l]*blo,r); i++) {
            if(f) v[i]=v[i]*c%mod;
            else v[i]=(v[i]+c)%mod;
        }
        if(bl[l]!=bl[r]) {
            reserve(bl[r]);
            for(int i=(bl[r]-1)*blo+1; i<=r; i++)
                if(f) v[i]=v[i]*c%mod;
                else v[i]=(v[i]+c)%mod;
        }
        for(int i=bl[l]+1; i<=bl[r]-1; i++) {
            if(f) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod;
            else atag[i]=(atag[i]+c)%mod;
        }
    }
    
    int main() {
        scanf("%d",&n);
        blo=sqrt(n);
        for(int i=1; i<=n; i++) {
            scanf("%d",&v[i]);
            bl[i]=(i-1)/blo+1;
            mtag[bl[i]]=1;
        }
        for(int opt,l,r,c,i=1; i<=n; i++) {
            scanf("%d%d%d%d",&opt,&l,&r,&c);
            if(opt==2) printf("%d
    ",(v[r]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod);
            else add(opt,l,r,c);
        }
    
        return 0;
    }

    P3373 【模板】线段树 2

    自己的分块自带大常数,qwq,只能做$70$分,还是太弱啦。。。

    // luogu-judger-enable-o2
    # pragma GCC optimize "O3"
    #include<bits/stdc++.h>
    
    #define N 200005
    #define LL long long
    #define IL inline
    #define RG register
    using namespace std;
    
    IL void in(RG LL &x){
        register char c=getchar();x=0;int f=1;
        while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
        while(isdigit(c)){x=x*10+c-'0';c=getchar();}
        x*=f;
    }
    
    IL void print(RG LL x){
        if(x>9) print(x/10);
        putchar(x%10+'0');
    }
    LL n,v[N],mtag[N],atag[N],bl[N],blo,mod,m,sum[N];
    
    IL void reserve(RG LL x) {
        for(LL i=(x-1)*blo+1; i<=min(x*blo,n); i++)
            v[i]=(v[i]*mtag[x]%mod+atag[x]%mod)%mod;
        atag[x]=0,mtag[x]=1;
    }
    
    IL void add(RG LL f,RG LL l,RG LL r,RG LL c) {
        reserve(bl[l]);
        for(RG LL i=l; i<=min(bl[l]*blo,r); i++) {
            if(f==1) sum[bl[l]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
            else v[i]=(v[i]+c)%mod,sum[bl[l]]+=c;
            sum[bl[l]]%=mod;
        }
        if(bl[l]!=bl[r]) {
            reserve(bl[r]);
            for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++) {
                if(f==1) sum[bl[r]]+=v[i]*(c-1)%mod,v[i]=v[i]*c%mod;
                else v[i]=(v[i]+c)%mod,sum[bl[r]]+=c;
                sum[bl[r]]%=mod;
            }
            for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) {
                if(f==1) atag[i]=atag[i]*c%mod,mtag[i]=mtag[i]*c%mod,sum[i]=c*sum[i]%mod;
                else atag[i]=(atag[i]+c)%mod,sum[i]=(sum[i]+blo*c)%mod;
            }
        }
    }
    
    IL LL query(RG LL l,RG LL r) {
        RG LL ans=0;
        for(RG LL i=l; i<=min(bl[l]*blo,r); i++)
            ans=(ans+v[i]*mtag[bl[l]]%mod+atag[bl[l]]%mod)%mod;
        if(bl[l]!=bl[r]) {
            for(RG LL i=(bl[r]-1)*blo+1; i<=r; i++)
                ans=(ans+v[i]*mtag[bl[r]]%mod+atag[bl[r]]%mod)%mod;
        }
        for(RG LL i=bl[l]+1; i<=bl[r]-1; i++) ans=(ans+sum[i])%mod;
    
        return ans;
    }
    
    int main() {
        in(n),in(m),in(mod);
        blo=sqrt(n);
        for(RG LL i=1; i<=n; i++) {
            in(v[i]);
            bl[i]=(i-1)/blo+1;
            mtag[i]=1;
            sum[bl[i]]=(sum[bl[i]]+v[i])%mod;
        }
        
        for(RG LL opt,l,r,c,i=1; i<=m; i++) {
            in(opt),in(l),in(r);
            if(opt==3) print(query(l,r)%mod),putchar('
    ');
            else in(c),add(opt,l,r,c);
        }
    
        return 0;
    }
    分块——线段树2
  • 相关阅读:
    Scala-函数
    Scala--循环
    scala(一)
    拦截器filter
    Ajax实现分页二
    并发1
    泛型
    协议protocol
    结构体structure
    类的继承
  • 原文地址:https://www.cnblogs.com/song-/p/9470487.html
Copyright © 2020-2023  润新知