zoukankan      html  css  js  c++  java
  • 树状数组简易教程

    前言

    树状数组由于其优越的常数,优越的代码量,更小的空间占用,所以常被使用。(实际上(ZKW)线段树常数也小,码量也小,功能更强大,但是较难理解所以哪位大佬教我一下)下面就较为详细地介绍树状数组。

    然而本博客可能写得较难理解……

    树状数组原理

    如果我们令一个数组(A[1]=a[1], A[2]=a[1]+a[2],A[3]=a[3],A[4]=a[1]+a[2]+a[3]+a[4])……换言之,如果当前下标二进制中最低位的(1)是第(i)位,我们令当前的元素存储的就是当前位置及前面(2^i)个元素。

    你就会发现它有一些好玩的性质:它可以(O(logn))修改单个元素与(O(logn))查询前缀和。这个可在下面程序中配合这段话理解一下。

    这里有个注意点,由于树状数组询问求得的是前缀和,所以并不能维护最大最小值。

    一维树状数组

    单点修改,区间查询

    根据上面的定义,我们可以这样修改单个元素:

    void Add( int Index, int Value ) {
        for( int i = Index; i <= n; i += i & -i ) Tree[ i ] += Value;
        return;
    }
    

    其中(i&-i)(lowbit)。若它的二进制最低位(1)在第(i)位,那么它的返回值就是(2^i)

    同样的我们可以这样查询前缀和:

    int Query( int Index ) {
        int Ans = 0;
        for( int i = Index; i; i -= i & -i ) Ans += Tree[ i ];
        return Ans;
    }
    

    那么区间([x,y])和就是(Query(y)-Query(x-1))

    到这里,你已经学会了最基础的线段树。

    区间修改,单点查询

    其实,如果我们希望将区间([x,y])加上(v),我们只需在(x)位置加(v),再将(y+1)位置减(v),那么前缀和就是所求答案了。

    区间修改,区间查询

    如果仅用上面的操作,我们可能无法在理想的复杂度内完成区间修改运算。

    我们考虑维护另一个东西,使得它的前缀和是我们当前想维护的数组。那么如果我们当前想维护的数组为(A),那么我们令(B_i=A_i-A_{i-1})即可。

    考虑修改区间。我们只要将(B[x])(v)(B[y+1])(v),那么对应的(A[x])(A[y])就都加上了(v)

    考虑如何求值。

    [Ans=sum_{i=1}^NA[i]=sum_{i=1}^Nsum_{j=1}^iB[i]=Nsum_{i=1}^NB[i]-sum_{i=1}^N(i-1)B[i] ]

    那么我们就只要维护(B[i])((i-1)B[i])就好了。

    代码:

    void Add( LL *a, LL Index, LL V ) {
    	for( LL i = Index; i <= n + 1; i += i & -i ) a[ i ] += V;
    	return;
    }
    
    LL Ask( LL *a, LL Index ) {
    	LL Ans = 0;
    	for( LL i = Index; i; i -= i & -i ) Ans += a[ i ];
    	return Ans;
    }
    
    LL Query( LL x ) {
    	return x * Ask( Tree1, x ) - Ask( Tree2, x );
    }
    
    //区间修改
    Add( Tree1, l, x ); Add( Tree1, r + 1, -x );
    Add( Tree2, l, ( l - 1 ) * x ); Add( Tree2, r + 1, -r * x );
    //区间求和
    Query( r ) - Query( l - 1 )
    

    二维树状数组

    原理

    跟一维的情况相同,每一维的坐标分别维护如一维时的一段长度。所以实际上二维树状数组每个元素是一个矩形的和,每个操作的复杂度也变为了两只(log)

    而求和时的前缀和便变成了(Query(x_2,y_2)-Query(x_1-1,y_2)-Query(x_2,y_1-1)+Query(x_1-1,y_1-1))

    单点修改,区间查询

    不多解释。

    void Add( LL x, LL y, LL c ) {
    	for( LL i = x; i <= n; i += i & -i )
    		for( LL j = y; j <= m; j += j & -j )
    			A[ i ][ j ] += c;
    	return;
    }
    
    LL Query( LL x, LL y ) {
    	LL Ans = 0;
    	for( LL i = x; i; i -= i & -i )
    		for( LL j = y; j; j -= j & -j )
    			Ans += A[ i ][ j ];
    	return Ans;
    }
    
    LL Ans( LL a, LL b, LL c, LL d ) {
    	--a; --b;
    	return Query( c, d ) - Query( a, d ) - Query( c, b ) + Query( a, b );
    }
    

    区间修改,区间查询

    思路如同上文的一维树状数组。我们令(B_{i,j}=A_{i,j}-A_{i-1,j}-A_{i,j-1}+A_{i-1,j-1})

    修改区间就是(B_{x_1,y_1}+=v,B_{x_2+1,y_1}-=v,B_{x_1,y_2+1}-=v,B_{x_2+1,y_2+1}+=v)

    然后区间询问我们就强推一波式子:

    [egin{aligned} sum_{i=1}^{X}sum_{j=1}^{Y}A_{i,j}&=sum_{i=1}^{X}sum_{j=1}^{Y}sum_{k=1}^isum_{l=1}^j B_{k,l}\&=sum_{i=1}^{X}sum_{j=1}^{Y}B_{i,j}(X-i+1)(Y-j+1)\&=(X+1)(Y+1)sum_{i=1}^{X}sum_{j=1}^{Y}B_{i,j}-(X+1)sum_{i=1}^{X}sum_{j=1}^{Y}jB_{i,j}-(Y+1)sum_{i=1}^{X}sum_{j=1}^{Y}iB_{i,j}+sum_{i=1}^{X}sum_{j=1}^{Y}ijB_{i,j} end{aligned} ]

    于是我们发现只要维护(4)个二维树状数组,分别是(B_{i,j},iB_{i,j},jB_{i,j},ijB_{i,j})。(雾)

    附上程序:

    void add( ll x, ll y, ll v ) {
    	for( ll i = x; i <= n; i += i & -i )
    		for( ll j = y; j <= m; j += j & -j ) {
    			a[ i ][ j ][ 0 ] += v;
    			a[ i ][ j ][ 1 ] += y * v;
    			a[ i ][ j ][ 2 ] += x * v;
    			a[ i ][ j ][ 3 ] += x * y * v;
    		}
    	return;
    }
    
    ll ask( ll x, ll y ) {
    	ll ans = 0;
    	for( ll i = x; i; i -= i & -i )
    		for( ll j = y; j; j -= j & -j )
    			ans += ( x + 1 ) * ( y + 1 ) * a[ i ][ j ][ 0 ]
    				- ( x + 1 ) * a[ i ][ j ][ 1 ]
    				- ( y + 1 ) * a[ i ][ j ][ 2 ]
    				+ a[ i ][ j ][ 3 ];
    	return ans;
    }
    
    ll query( ll a, ll b, ll c, ll d ) {
    	return ask( c, d ) - ask( a - 1, d ) - ask( c, b - 1 ) + ask( a - 1, b - 1 );
    }
    

    结语

    垃圾极简教程就到这里了,大家虐题愉快呀。

  • 相关阅读:
    树莓派设置CPU运行的核心数为3,保留核心4号
    node+egg中mongdb的一些知识点
    如何提高前端的技能和快速涨薪?
    【安全认证】我的CISSP达成之路
    前端gitLab ci/cd搭建
    flutter调试
    js rgb hex hsv色值转换
    Error waiting for a debug connection: The log reader stopped unexpectedly
    Flutter滑动列表实现
    前端异常监控
  • 原文地址:https://www.cnblogs.com/chy-2003/p/10152743.html
Copyright © 2011-2022 走看看