[数据结构学习]分块与树状数组

分块与树状数组均在区间问题上有重要的应用

emm分块效率上不如树状数组,但是思路比较好想

先说分块:

将n个数的序列分为sqrt(n)块,预处理每块数据的信息以加快后续对区间信息的查询

先上一段代码:

const int maxn = 5e5 + 50;
int sum[maxn],a[maxn],l[maxn],r[maxn],belong[maxn];
int block,num;

void build(){
    block = sqrt(n);
    num = n/block; if(n%block) num++;
    for(int i = 1; i <= num; i++)
        l[i] = (i-1)*block+1, r[i] = i*block;
    r[num] = n;

    for(int i = 1; i <= num; i++){
        for(int j = l[i]; j <= r[i]; j++)
            belong[j] = i, sum[i] += a[j];
    }
}

代码中的全局变量有这些是需要预处理的:sum[i]表示第i块数据的和(也可是异或和、乘积这类,一样的)

a[i]为第i个数据,l[i],r[i]分别为第i块的左右端点下标,belong[i]存储第i个数所属的分块

预处理过后,贴上单点修改/更新和区间查询的代码:

inline void update(int x, int y){
    a[x] += y;
    sum[belong[x]] += y;
}

inline int query(int x, int y){
    int ans = 0;
    if(belong[x] == belong[y]){
        for(int i = x; i <= y; i++)
            ans += a[i];
        return ans;
    }
    for(int i = x; i <= r[belong[x]]; i++)
        ans += a[i];
    for(int i = belong[x] + 1; i < belong[y]; i++)
        ans += sum[i];
    for(int i = l[belong[y]]; i <= y; i++)
        ans += a[i];
    return ans;
}

其中对单点进行数据更新只需将数组a中数据改变,再将下标为x所属的块的总和增加y即可

而查询区间[x,y]的和,分块这里采用的是暴力求和

1.当x,y在同一块中时,遍历求和

2.当x,y不在同一块,遍历求和x到x所属块的右边界,再加上sum[x+1]到sum[y-1],再遍历求和y所属块的左边界到y

时间复杂度均为O(sqrt(n)) 单点修改为O(1)

OK继续:

树状数组,用途较分块而言应用的更加广泛,单点修改与区间和的查询效率均为对数复杂度

也可用于同时需要进行单点查询和区间修改/区间查询和区间修改的情况

树状数组长这样:

用一个数组tree来辅助原数组a的存储(也就是我们建立的这个树状数组)

其中tree[i] = a[i - 2^k + 1] +……+ a[i] (k为i的二进制位末尾连续0的个数, 2^k有专门的一个名字:lowbit)

顺便:lowbit(i) = i&-i (主要是利用了二进制数在计算机内存储的性质,我没深究。)

OK,这是建树状数组的一个基础的式子。

当我们需要来对序列中某个值进行更新时(改变/加减数之类的),比如a[i] += k

就需要改变tree数组内所有与a[i]相关的值,即tree[i+2^k],tree[(i+2^k) + 2^k']……

依此反复迭代更改i的值并更新即可

接下来是求和 前i项的和sumi = c[i] + c[i - 2^k] + c[(i - 2^k) - 2^k']…

【PS:至于树状数组为啥这么定义k,为什么sum的计算是这样的,其实我并没有搞得很清楚,我个人感觉树状数组这个东西更像是强行利用了二进制人造出来的一个方便使用的毫无数学美感的数据结构,反正我学的时候感觉到很让人不理解,各种资料也没有讲明白定义的依据。】

知道如何维护和查询后就可以用了。

附上代码:

const int maxn = 5e5 + 5;

int a[maxn],c[maxn];
int n; inline
int lowbit(int i){ return i&(-i); } void update(int i, int x){
  a[i] += x;
while(i <= n){ c[i] += x; i += lowbit(i); //反复迭代更新与a[i]有关的值 } } int query(int x, int y){ int sumx, sumy; sumx = sumy = 0; while(x>0){ sumx += c[x]; x -= lowbit(x); } while(y>0){ sumy += c[y]; y -= lowbit(y); //反复迭代求解sum = a[i] + a[i-lowbit(i)] + a[(i-lowbit(i)) - lowbit(i')]… } return sumy - sumx; }

因为树状数组是搭建在二进制的基础上的,在迭代求和/更新的过程中复杂度为O(logn)的

而初始化树状数组的过程相当于把原数组中的0更新为a[i],故在输入数据时调用update即可初始化树状数组

----------------------------------分割线qwq-------------------------

而当树状数组需要同时进行单点查询和一整个区间的进行修改时

我们对原序列的差分初始化树状数组

(差分求sumi相当于求原序列的a[i]值, 而对整个区间进行整体修改对区间内部的差分无影响,仅对端点的差分有影响

因此区间更新只需要update两个端点值即可)

复杂度均为O(logn)

嘛qwq 还有同时可以高效率进行区间查询和区间修改的,需要维护两个树状数组

qwqwq再次再次改天再写(qwqwq我会说是我还没搞清楚嘛=-=犯懒了,回头再看)

原文地址:https://www.cnblogs.com/leafsblogowo/p/12668906.html