分块 学习笔记

前言:内容参考自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\n",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\n",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\n",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\n",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;
}

 

posted @ 2020-06-26 18:14  我亦如此向往  阅读(157)  评论(0编辑  收藏  举报