zoukankan      html  css  js  c++  java
  • 分块 学习笔记

    前言:内容参考自https://www.luogu.com.cn/blog/expect/solution-p2801。感谢。

    ---------------------

    分块,是一种优雅的暴力,它通过对数列分段,完成对数列一些区间操作和区间查询的操作,是一种根号算法。本文属于分块入门笔记,旨在零基础的同学学会分块。

    1 建块

    在建块伊始,我们需要完成一下几个任务:

    1.确定块的大小

    2.确定块的数量

    3.标记每个块的左右边界

    4.标记每个元素所属的块

    5.对块内的元素进行预处理

    1.1.块的大小:为了保证时间复杂度最优,一般我们让块的大小为$sqrt(n)$。写起来就一句话:

    int size=sqrt(n);

    1.2.块的数量:显然为$n/size$。如果$n$不是完全平方数,则让数量+1。

    int tot=n/size;
    if (n%tot) tot++;

    1.3.块的端点:对于每个块的左右端点,显然$L[1]=1,R[1]=block,L[2]=R[1]+1,R[2]=block*2,cdots$,所以我们可以得到:

    for (int i=1;i<=tot;i++)
        L[i]=(i-1)*size+1,R[i]=i*size;

    1.4.所属的块:同样,我们可以得到每个元素所属的块:

    for (int i=1;i<=n;i++) belong[i]=(i-1)/size+1;

    1.5.初始化:根据题目的要求来进行操作。一般都有预处理区间和,或者对元素进行排序。

    2.修改

    分块题常见的操作就是区间修改。对于修改区间$(x,y)$,我们一般这样处理:

    $(x,y)$的左边和右边可能不是属于一个整块的。对于这些边边角角,我们暴力处理即可。

    对于中间的整块,我们考虑线段树区间修改的思想:$lazy tag$。我们只需让$add[i]+=k$即可,这就是分块算法较为高效的地方。

    注意特判$belong[x]==belong[y]$的情况,此情况暴力处理即可。

    代码如下(以洛谷P2801为例):

    inline void update()
    {
        if (belong[x]==belong[y])//特判
        {
            for (int i=x;i<=y;i++) a[i]+=k;
            for (int i=L[belong[x]];i<=R[belong[x]];i++) d[i]=a[i];
            sort(d+L[belong[x]],d+R[belong[x]]+1);
            return;
        }
        for (int i=x;i<=R[belong[x]];i++) a[i]+=k;//暴力处理
        for (int i=L[belong[x]];i<=R[belong[x]];i++) d[i]=a[i];
        sort(d+L[belong[x]],d+R[belong[x]]+1);
        for (int i=L[belong[y]];i<=y;i++) a[i]+=k;
        for (int i=L[belong[y]];i<=R[belong[y]];i++) d[i]=a[i];
        sort(d+L[belong[y]],d+R[belong[y]]+1);
        for (int i=belong[x]+1;i<=belong[y]-1;i++) add[i]+=k;//使用lazy tag。因为整块内元素相对大小不变,所以无需进行重新排序。
    }

    3.查询

    如果是查询区间和,那么直接$ans+=sum[i]+add[i]*(R[i]-L[i]+1)$即可。如果是进行大小的查询,因为块内我们已经排过序,元素满足单调性,所以我们可以进行二分查找。

    跟修改块一样,我们同样是暴力处理边边角角,对于整块二分查找。

    代码如下(以洛谷P2801为例):

    inline void query()
    {
        int ans=0;
        if (belong[x]==belong[y])//特判
        {
            for (int i=x;i<=y;i++) if (add[belong[x]]+a[i]>=k) ans++;
            printf("%d
    ",ans);
            return;
        }
        for (int i=x;i<=R[belong[x]];i++) if (add[belong[x]]+a[i]>=k) ans++;//暴力
        for (int i=L[belong[y]];i<=y;i++) if (add[belong[y]]+a[i]>=k) ans++;
        for (int i=belong[x]+1;i<=belong[y]-1;i++)
        {
            int ll=L[i],rr=R[i],res=0;
            while(ll<=rr)//二分,其元素满足单调性
            {
                int mid=(ll+rr)/2;
                if (add[i]+d[mid]>=k) rr=mid-1,res=R[i]-mid+1;
                else ll=mid+1;
            }
            ans+=res;
        }
        printf("%d
    ",ans);
    }

    4.完整代码

    这里以洛谷P2801为例。这题有一个小细节,我们应该开两个数组:操作数组和原始数组。因为排序会把原有元素的顺序打乱,所以我们在暴力处理边边角角的时候要用到原始数组。对于整块处理因为是整体处理,所以直接用操作数组即可。

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    int a[1000005],d[1000005],L[1005],R[1005],belong[1000005],add[1005];
    int n,q,block,tot,x,y,k;
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void build()
    {
        block=sqrt(n);tot=n/block;
        if (n%block) tot++;
        for (int i=1;i<=n;i++) belong[i]=(i-1)/block+1,d[i]=a[i];
        for (int i=1;i<=tot;i++) L[i]=(i-1)*block+1,R[i]=i*block;
        R[tot]=n;
        for (int i=1;i<=tot;i++) sort(d+L[i],d+R[i]+1);
    }
    inline void update()
    {
        if (belong[x]==belong[y])
        {
            for (int i=x;i<=y;i++) a[i]+=k;
            for (int i=L[belong[x]];i<=R[belong[x]];i++) d[i]=a[i];
            sort(d+L[belong[x]],d+R[belong[x]]+1);
            return;
        }
        for (int i=x;i<=R[belong[x]];i++) a[i]+=k;
        for (int i=L[belong[x]];i<=R[belong[x]];i++) d[i]=a[i];
        sort(d+L[belong[x]],d+R[belong[x]]+1);
        for (int i=L[belong[y]];i<=y;i++) a[i]+=k;
        for (int i=L[belong[y]];i<=R[belong[y]];i++) d[i]=a[i];
        sort(d+L[belong[y]],d+R[belong[y]]+1);
        for (int i=belong[x]+1;i<=belong[y]-1;i++) add[i]+=k;
    }
    inline void query()
    {
        int ans=0;
        if (belong[x]==belong[y])
        {
            for (int i=x;i<=y;i++) if (add[belong[x]]+a[i]>=k) ans++;
            printf("%d
    ",ans);
            return;
        }
        for (int i=x;i<=R[belong[x]];i++) if (add[belong[x]]+a[i]>=k) ans++;
        for (int i=L[belong[y]];i<=y;i++) if (add[belong[y]]+a[i]>=k) ans++;
        for (int i=belong[x]+1;i<=belong[y]-1;i++)
        {
            int ll=L[i],rr=R[i],res=0;
            while(ll<=rr)
            {
                int mid=(ll+rr)/2;
                if (add[i]+d[mid]>=k) rr=mid-1,res=R[i]-mid+1;
                else ll=mid+1;
            }
            ans+=res;
        }
        printf("%d
    ",ans);
    }
    signed main()
    {
        n=read(),q=read();
        for (int i=1;i<=n;i++) a[i]=read();
        build();
        for (int i=1;i<=q;i++)
        {
            char c;cin>>c;
            x=read(),y=read(),k=read();
            if (c=='M') update();
            else query();
        }
        return 0;
    }
  • 相关阅读:
    3星|《失败课》:投资人写给创业者的经验谈,有点标题党
    3星|《给你一门人工智能入门生意经》:机器所知胜于其所能言传
    3星|《财经》2018年第5期:西伯利亚冻土层的猛犸象牙是合法的,一根能卖到数万美元
    3星|《增长黑客》:增长黑客是一个牵强的概念
    2星|《只管去做》:做年度计划的入门级介绍,信息浓度太低
    创业者融资过程中需要了解的大坑小坑:《风投的技术》,4星
    4星|吴军《见识》:李开复上级的工作经验、投资经验与人生忠告
    4星|《基因转》:从孟德尔、达尔文到人类胚胎转基因
    3星|《人机平台》:数字化时代的三大类新的再平衡:人脑与机器、产品与平台,以及核心与大众
    3星|《知识的边界》:知识存在于网络中,分歧永远存在
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13195875.html
Copyright © 2011-2022 走看看