分塊

學莫隊之前先看了看分塊,,總結一下的話感覺還是一個暴力的算法

一、什麼時候可以用到分塊呢?

如果問,一個數列,給定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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章