分块入门

分块,一直以来都听过这个,但是从来没有用过,发现其实很多题目都可以用分块卡过去,突然想起来学分块的原因也是因为两周前的西安邀请赛有一题,看到网上有人说用分块可以做,而且貌似很裸? 心想要是当时会的话就金了啊。(或者把当时脑抽没想到的线段树做出来TAT)

不说废话了,直接进入正题,分块,顾名思义,就是把很长的一整个区间,分成几个小的区间去处理,降低时间复杂度。(其实就和莫队思想差不多)

那么我们究竟分成多少块呢?答案是 n

因为这样我们分出来的每一段区间的数字个数就是 n

区间个数和每个区间所维护的数字个数相等,所以时间复杂度是比较优的。

比如n=100的时候,分成了10个区间,每个区间维护10个数。
有人问n=105的时候怎么办,那我们就多分一个区间,共11个区间,前10个区间每个区间维护10个数,第11个区间维护5个数就好了(换句话说就是余数单独分一个区间,并且可知余数不超过 n )。

我们先看一道裸题。

初始时给你n个数字( n <= 100000 ),至多q次操作( q <= 100000 ),操作分为两种:
1 x y表示将第x个数字加上y
2 x y表示求[x,y]区间的最大值

有人一看卧槽这不是线段树或树状数组裸题吗?是的,线段树和树状数组完全可以做,但是分块怎么做呢?

可能大家一下子就想出来了,我们将n个数字分成 n 个区间之后,每个区间维护一个最大值,在第一种操作的时候,我们修改一下单点的值并维护所在的区间,在第二种操作时,我们扫描一遍从x到y的区间,注意,我们并不是暴力地扫描每一个点,而是如果某个区间被包括在[x,y]的范围内,我们就直接看区间维护的最大值就可以了,而对于只有一部分在[x,y]范围内的区间,我们就暴力看每个数就行了。

我们可以推出这样做时间复杂度是 n n

为什么呢,单点更新的时候,显然是 O ( 1 )
而查询的时候,对于全部包含在[x,y]中的区间,我们至多需要查看 n 个区间,而对于不完全被包含在[x,y]中的区间,我们暴力地去查看,也只需要看至多两个区间(左端点和右端点所在的区间),共 2 ( n 1 ) 个数,所以总的时间复杂度还是 n

n 次操作,那就是 O ( n n )

我们可以看到这个时间复杂度是不如线段树 O ( n l o g n ) 的,但是也是可以接受,并且能过很多题的。

具体代码怎么写呢?这里参考卿学姐的写法(orz)。

#include <iostream>
#include <math.h>
#include <string.h>
#include <stdio.h>
#include <algorithm>

using namespace std;
const int maxn = 100050;
int n,q,block,l[maxn],r[maxn],num,belong[maxn],Max[maxn];
//block表示每个区间所维护的数的个数,num表示区间个数
//l[i]和r[i]表示第i个区间的左右端点,belong[i]表示第i个值所在的区间
//Max[i]表示第i个区间的最大值
int a[maxn];
void build()
{
    int block=sqrt(n);
    int num=n/block;
    if(n%block)//如果有余数则需要另开一个区间
        num++;
    for(int i=1;i<=num;i++)
    {
        l[i]=(i-1)*block+1;
        r[i]=i*block;
    }
    r[num]=n;//最后一个区间右端点特殊处理
    memset(Max,0,sizeof(Max));
    for(int i=1;i<=n;i++)//初始化Max数组
    {
        belong[i]=(i-1)/block+1;
        Max[belong[i]]=max(Max[belong[i]],a[i]);
    }
}

void update(int pos,int y)
{
    a[pos]+=y;
    Max[belong[pos]]=max(Max[belong[pos]],a[pos]);
}
int query(int x,int y)
{
    int ll=belong[x];
    int rr=belong[y];
    if(ll==rr)//x和y在同一个区间的情况
    {
        int ans=0;
        for(int i=x;i<=y;i++)
            ans=max(ans,a[i]);
        return ans;
    }
    else
    {
        int ans=0;
        for(int i=x;i<=r[ll];i++)//处理x所在的区间
            ans=max(ans,a[i]);
        for(int i=ll+1;i<=rr-1;i++)//中间的区间
            ans=max(ans,Max[i]);
        for(int i=l[rr];i<=y;i++)//处理y所在的区间
            ans=max(ans,a[i]);
        return ans;
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build();
    while(q--)
    {
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if(t==1)
            update(x,y);
        else
            printf("%d\n",query(x,y));
    }
    return 0;
}

这样一道简单的分块就写完了
也是今天刚会的一道算法,讲得比较浅,以后有机会再写分块。

猜你喜欢

转载自blog.csdn.net/qq_36614777/article/details/80509342