分块

本文介绍了分块算法在处理数列查询和修改问题中的应用。当需要对包含加减操作的区间求和或查询区间内大于等于k的数时,分块算法能有效解决问题。分块的基本思想是将数列平均分为sqrt(n)或sqrt(n)+1块,通过标记数组和暴力枚举处理更新和查询。时间复杂度为O(sqrt(n)),适用于数据规模较大的情况。文章以洛谷P2801题目为例,帮助理解分块算法的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学莫队之前先看了看分块,,总结一下的话感觉还是一个暴力的算法

一、什么时候可以用到分块呢?

如果问,一个数列,给定m次询问,每次问一个区间内的和是多少?

     很明显可以用前缀和来解决

那么如果问还要对随机一个区间进行加或减之类的修改呢?

     很容易想到树状数组和线段树来解决

再问,如果在加上问一个区间大于等于k的数有几个呢?

这个时候就需要这个‘暴力’的方法出场了,我们可以用分块解决。

二、什么是分块?

一个有n个数的数列,将它平均分为sqrt(n)或sqrt(n)+1块,这里分成多少块取决于sqrt(n)是否是一个整数。

然后每次更新时对于一个整块可以直接进行更新,常用的是用一个atag标记数组标记,而对于那些不是一个整块

的范围,直接暴力枚举就可以了。

查询的时候也是这样,对于整块通常使用二分查询,不是整块的依然是暴力查询。

那么为什么要分成sqrt(n)块呢?

因为如果分的块数太多,累加块的时候就会变慢,而如果分的太少,每次对于一个块的累加就会变慢,综上,分成sqrt(n)

是最理想的分法。

分块的时间复杂度是O(sqrt(n)),为什么呢?

对于整个块的累加是O(sqrt(n)),而对于散的块累加,因为散的块的数不会超过sqrt(n),所以时间复杂度最多依然是sqrt(n)。

三、块的建立

因为查询和更新要因题而异

这里就说一下块的建立

void build()
{
    blo=sqrt(n);         //blo为每一块有多少个
    for(int i=1;i<=n;i++){
            b[i]=a[i];       //b数组用来排序原数组,以便最后二分查找时用
        bl[i]=(i-1)/blo+1;    //bl[i]表示第i个属于第几块
    }
    for(int i=1;i<=bl[n];i++){          //bl[n]表示的是最后一个元素在第几个块里,即一共分成了多少块
        l[i]=(i-1)*blo+1;       //l[i]表示第i块的左端点是第几个元素
        r[i]=blo*i;           //r[i]表示第i块的右端点是第几个元素
    }
    r[bl[n]]=n;      //最后一个块的右端点必然是n
    for(int i=1;i<=bl[n];i++)
    sort(b+l[i],b+r[i]+1);
}

前面也提到了分块很“暴力”,,所以如果遇到大的数据范围就不要考虑它了......

 

这里给出一个例题洛谷p2801

如果还有对更新和查询操作有疑问的可以结合这个例题看一下

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e6+10;
int a[N],b[N],l[N],r[N],bl[N],blo,atag[N],n,m;      //bl[i]表示第i个属于第几块
void build()
{
    blo=sqrt(n);         //blo为每一块有多少个
    for(int i=1;i<=n;i++){
            b[i]=a[i];       //b数组用来排序原数组,以便最后二分查找时用
        bl[i]=(i-1)/blo+1;    //bl[i]表示第i个属于第几块
    }
    for(int i=1;i<=bl[n];i++){          //bl[n]表示的是最后一个元素在第几个块里,即一共分成了多少块
        l[i]=(i-1)*blo+1;       //l[i]表示第i块的左端点是第几个元素
        r[i]=blo*i;           //r[i]表示第i块的右端点是第几个元素
    }
    r[bl[n]]=n;      //最后一个块的右端点必然是n
    for(int i=1;i<=bl[n];i++)
    sort(b+l[i],b+r[i]+1);
}
void reset(int x)          //每次更新完重新排序
{
    for(int i=l[x];i<=r[x];++i)
        b[i]=a[i];
    sort(b+l[x],b+r[x]+1);
}
void update(int x,int y,int z)
{
    if(bl[x]==bl[y]){         //如果在一个块中,直接处理
        for(int i=x;i<=y;++i)
            a[i]+=z;
        reset(bl[x]);
        return ;
    }
    for(int i=x;i<=r[bl[x]];i++)       //否则暴力处理不是整块的,就是x所在的那个块
        a[i]+=z;
    reset(bl[x]);
    for(int i=l[bl[y]];i<=y;i++)    //暴力处理不是整块的,也就是y所在的那个块
        a[i]+=z;
    reset(bl[y]);
    for(int i=bl[x]+1;i<=bl[y]-1;i++)   //其余的整块在处理,累加到atag数组中
        atag[i]+=z;
}
int Find(int x,int z)        //二分
{
    int ll=l[x],rr=r[x];
    while(ll<=rr){
        int mid=(ll+rr)>>1;
        if(b[mid]<z)
            ll=mid+1;
        else rr=mid-1;
    }
    return r[x]-ll+1;
}
int query(int x,int y,int z)
{
    int ans=0;
    if(bl[x]==bl[y]){       //如果在一个块中,直接查询
        for(int i=x;i<=y;i++)
            if(a[i]+atag[bl[i]]>=z)
            ans++;
        return ans;
    }
    for(int i=x;i<=r[bl[x]];i++)       //否则依然是先处理散块
        if(a[i]+atag[bl[i]]>=z)
        ans++;
    for(int i=l[bl[y]];i<=y;i++)
        if(a[i]+atag[bl[i]]>=z)
        ans++;
    for(int i=bl[x]+1;i<=bl[y]-1;i++)      //最后对整块处理
        ans+=Find(i,z-atag[i]);
    return ans;
}
int main()
{
      scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
            build();
            int x,y,z;
            char c;
        for(int i=1;i<=m;i++){
                cin>>c;
            scanf("%d%d%d",&x,&y,&z);
            if(c=='M')
                update(x,y,z);
            else printf("%d\n",query(x,y,z));
        }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值