分块入门学习

学习博客:http://hzwer.com/8053.html

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

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

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

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

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

我们先看一道裸题。

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

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

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

我们可以推出这样做时间复杂度是nn−−√nn
为什么呢,单点更新的时候,显然是O(1)O(1)
而查询的时候,对于全部包含在[x,y]中的区间,我们至多需要查看n−−√n个区间,而对于不完全被包含在[x,y]中的区间,我们暴力地去查看,也只需要看至多两个区间(左端点和右端点所在的区间),共2(n−−√−1)2(n−1)个数,所以总的时间复杂度还是n−−√n
nn次操作,那就是O(nn−−√)O(nn)
我们可以看到这个时间复杂度是不如线段树O(nlogn)O(nlogn)的,但是也是可以接受,并且能过很多题的。
  

#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;
}

 

posted @ 2019-01-22 16:13  执||念  阅读(179)  评论(0编辑  收藏  举报