块状数组是一个非常有趣的数据结构,利用分块的思想将再简单不过的数组化腐朽为神奇,sqrt(n)虽不及log(n),

但是性价比还是很好的。

但是需要注意的是块状数组一定要从0开始,这样可以求出i在哪个块中 pos = i / s , 其中s 为一个块的规模。 

块状数组就是将数组以sqrt(size)为单位分割成块,对于每一块单独维护其相应的信息。

用这样的方法可以做很多线段树可以做的题目,例如A Simple Problem with Integers

 http://poj.org/problem?id=3468

题意: 给一个数列,M个操作 add l r c 把l到r的每一个数加上c ; query l r 查询l到r的区间和

这个题可以用块状数组做,借鉴线段树的lazy-tag思想。

对于每一块维护其前缀和,并对每一块加一个lazy标记,add表示该块所有数都统一加了多少,初始化为0 。

对于每一个查询操作

<1>如果l和r位于同一个块中 ,那么 ans = b[r] -b[l-1] +add * (r-l+1) ( 如果l位于块的开始位置 那么这个公式就有问题了)

<2>如果l和r不位于同一块中, 对于中间的每一个块 ans+= b[y]+add*(y-x+1) , 对于两侧的块用1的方法解决

时间复杂度最好O(1),最坏O(sqrt(n)) .

对于每一个修改操作

<1>如果l和r位于同一个块中,直接修改.

<2>如果不位于同一块,对于中间完整的块修改add即可,不完整的块一次修改每一个数。

时间复杂度最坏也是sqrt(n)。

 

块状数组也可以解决区间第K值这样的问题 Dynamic Ranking

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2112

题意 : 给一个数列,m个操作 , c i t  将a[i]改为t   , Q l r k 查询l..r区间的第k小数

同样是将数列分块,每一块均维护递增顺序,代码如下:

    scanf("%d%d",&n,&m);
    for (int i=0; i<n; ++i) scanf("%d",a+i);

    memcpy(b,a,sizeof(a));
    s = sqrt(n) , t= n /s ;
    if (n%s) ++t ;
    for (int i=0; i<t ; ++i)
    {
        int lb= i*s , rb = lb +s-1 ;
        if (rb>=n) rb=n-1;
        sort(b+lb, b+rb+1) ;
    }

 对于每一个询问,采用二分答案的方式,对于每一个二分得到的值进行验证。

 但是这里有一个问题,二分到的数值可能满足要求,但是原数列中未必有这个数。

 例如数列 : 1  3  5  7  10   

 第5小的数值有 8 9 10 这三个数,但真正合法的是10 

 我们可以去寻找比答案大1的数,即最小的数p满足数列中有k个比p小的数

 答案就是p-1。 二分的代码如下:

int query(int x, int y, int k)
{

    int l= 0 ,r = 1000000020, mid ;
    while (l < r)
    {
        mid = l + r >> 1 ;
        int k1= countnum(x,y,mid);
        if (k1>=k) r = mid ;
        else l = mid+1 ;
    }
    return l-1;
}

 

countnum函数 的原理就很简单了 ,代码

int  countnum(int x ,int y, int z)
{
    int x_pos = x /s , y_pos = y /s ,ret= 0 ;
    if (x_pos==y_pos) {
        for (int i=x ; i<=y; ++i)
         ret += a[i]<z ;
    } else {
        for (int i= x ; i<=x_pos*s +s -1 && i<n; ++i)
         ret+= a[i]<z ;
        for (int i=x_pos+1; i<=y_pos-1; ++i)
         ret+= getnum(i*s,i*s+s-1,z);
        for (int i=y_pos*s ; i<=y ;++i)
         ret+= a[i]<z ;
    }
    return ret ;
}

getnum函数是统计第i个块中小于z的个数,因为块中的数是递增的,可以用log(sqrt(n))的时间

完成,整个countnum的时间复杂度是sqrt(n)

 

posted on 2014-02-12 22:01  Zbeginer  阅读(626)  评论(0编辑  收藏  举报