1. 程式人生 > 實用技巧 >演算法淺談之分塊

演算法淺談之分塊

一、何為分塊

分塊演算法實質上是一種是通過分成多塊後在每塊上打標記以實現快速區間修改,區間查詢的一種演算法。其均攤時間複雜度為 \(O(\sqrt n)\)

分塊演算法相較於各種樹形資料結構,具有簡便易寫,方便除錯等多種優點。在同等資料規模下,如 \(1e5\) ,其時間效率並不會低太多,在考試時反而是一種有力的得分方法。

接下來講一下分塊演算法的基本操作及性質:

為了使得其有著最穩定的時間複雜度,我們經常講一個長度為 \(n\) 的序列分為 \(\sqrt n\)個大小為 \(\sqrt n\) 的塊,如果 \(n\) 不是完全平方數,則序列最右端會多出一個角塊

如下圖,就是一種序列的分塊:

該序列被分成了\(4\)個塊,前三個塊的大小都是3( \(\approx\sqrt 10\)),我們稱之為整塊

而最後一個塊大小不足\(\sqrt 10\),我們稱之為角塊

接下來我們想辦法獲取每一個數屬於哪一個塊

int n;//總個數
int block=sqrt(n);//每一塊大小
for(int i=1;i<=n;i++)
{
    belong[i]=(i-1)/block+1;//每一個數所在塊
}

二、區間查詢

給定一個長的為\(n\)數列,求出任意區間\([l,r]\)的最大值\((1<=l,r<=n)(l<=r)\)

還是拿這張圖,我們現在給每個點上加了一個權值,每個塊維護一下塊內最大值

當我們查詢任意一個區間 \([l,r]\) 時,如果 \(l\) 所在的塊與 \(r\) 所在的塊相同,如 \([1,2]\),則直接暴力查詢即可,時間複雜度 \(O(\sqrt n)\)

若其不在一個塊但是塊是相鄰的,一樣是暴力查詢,時間複雜度 \(O(\sqrt n)\)

若其塊不相鄰,如 \([1,10]\) ,我們先處理兩邊的邊塊角塊,先暴力查詢 \(1\)\(10\) 所在的塊內最大值,最後直接查詢中間塊內最大值即可,時間複雜度 \(O(\sqrt n)\)

所以總時間複雜度 \(O(\sqrt n)\)

三、區間修改

對於整塊修改,我們打個加法標記,即當前塊增加了多少,最大值相應的就增加了多少

而多於邊塊角塊,暴力修改,特判最大值即可

所以總時間複雜度也是 \(O(\sqrt n)\)

四、模板題及模板

模板題

#include <bits/stdc++.h>
using namespace std ;
const int MAXN = 100000 + 5 ;
int belong[ MAXN ] , a[ MAXN ] ;
int addv[ 400 ] ;
vector < int > vc[ 400 ] ;
int n , block , cnt ;
inline int read () {
    int tot = 0 , f = 1 ; char c = getchar () ;
    while ( c < '0' || c > '9' ) { if ( c == '-' ) f = -1 ; c = getchar () ; }
    while ( c >= '0' && c <= '9' ) { tot = tot * 10 + c - '0' ; c = getchar () ; }
    return tot * f ;
}
inline void init () {
    for ( int i = 1 ; i <= cnt ; i ++ )
        sort ( vc[ i ].begin () , vc[ i ].end () ) ;
}
inline void update ( int blockn ) {
    vc[ blockn ].clear () ; //暴力清除
    for ( int i = ( blockn - 1 ) * block ; i <= blockn * block ; i ++ )
        vc[ blockn ].push_back ( a[ i ] ) ; //暴力新增
    sort ( vc[ blockn ].begin () , vc[ blockn ].end () ) ; //別忘記重新排序
}
inline void modify ( int l , int r , int c ) {
    for ( int i = l ; i <= min ( r , belong[ l ] * block ) ; i ++ ) a[ i ] += c ; //暴力修改
    update ( belong[ l ] ) ;
    if ( belong[ l ] != belong[ r ] ) {
        for ( int i = ( belong[ r ] - 1 ) * block ; i <= r ; i ++ ) a[ i ] += c ; //暴力修改
        update ( belong[ r ] ) ;
    }
    for ( int i = belong[ l ] + 1 ; i < belong[ r ] ; i ++ ) addv[ i ] += c ; //打標記
}
inline int query ( int l , int r , int c ) {
    int ans = -0x3f3f3f3f ;
    for ( int i = l ; i <= min ( r , belong[ l ] * block ) ; i ++ )
        if ( a[ i ] + addv[ belong[ i ] ] < c ) ans = max ( ans , a[ i ] + addv[ belong[ i ] ] ) ; //暴力查詢
    if ( belong[ l ] != belong[ r ] ) {
        for ( int i = ( belong[ r ] - 1 ) * block + 1 ; i <= r ; i ++ )
            if ( a[ i ] + addv[ belong[ i ] ] < c ) ans = max ( ans , a[ i ] + addv[ belong[ i ] ] ) ; //暴力查詢
    }
    for ( int i = belong[ l ] + 1 ; i < belong[ r ] ; i ++ ) {
        if ( vc[ i ][ 0 ] + addv[ i ] >= c ) continue ;
        int k = lower_bound ( vc[ i ].begin () , vc[ i ].end () , c - addv[ i ] ) -vc[ i ].begin () ; //因為我們已經排過序,所以每個塊中具有單調性,可以直接二分
        ans = max ( ans , vc[ i ][ k - 1 ] + addv[ i ] ) ;
    }
    return ans ;
}
signed main () {
    n = read () ;
    block = sqrt ( n ) ;//整塊的大小
    for ( int i = 1 ; i <= n ; i ++ ) {//預處理每個數在哪個塊
        a[ i ] = read () ;
        belong[ i ] = ( i - 1 ) / block + 1 ;
        vc[ belong[ i ] ].push_back ( a[ i ] ) ;每個塊有哪些數
        if ( i % block == 1 ) cnt ++ ;//cnt為總塊數
    }
    init () ;//先把每個塊的數從小到大排序
    for ( int i = 1 ; i <= n ; i ++ ) {
        int opt = read () , l = read () , r = read () , c = read () ;
        if ( opt == 0 ) modify ( l , r , c ) ;
        else {
            int res = query ( l , r , c ) ;
            if ( res == -0x3f3f3f3f ) {
                printf ( "-1\n" ) ;
                continue ;
            }
            printf ( "%d\n" , res ) ;
        }
    }
    return 0 ;
}

參考博文:https://www.luogu.com.cn/blog/deco/qian-tan-ji-chu-gen-hao-suan-fa-fen-kuai