• 分块 学习笔记


    前言:内容参考自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;
    }
  • 相关阅读:
    Intel CPU编号详解
    matplotlib(二)——matplotlib控制坐标轴第一个刻度到原点距离
    matplotlib(一)——matplotlib横轴坐标密集字符覆盖
    博客园定制页面(五)——使用自定义JS脚本(公告栏显示时间)
    PyCharm(二)——PyCharm打开本地项目不显示项目文件
    nohup命令
    URL是如何解析的
    Python 得到主机字节序
    Python 得到ISP地址
    亚马逊s3上传大文件Python
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13195875.html
Copyright © 2020-2023  润新知