算法浅谈之分块

一、何为分块

分块算法实质上是一种是通过分成多块后在每块上打标记以实现快速区间修改,区间查询的一种算法。其均摊时间复杂度为 \(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

猜你喜欢

转载自www.cnblogs.com/hulean/p/13397162.html