今天看了一下树状数组,个人认为树状数组是一个很优美的数据结构,虽然看的并不是很懂。。。一些简单的知识就不讲的,请自行百度。。不过还是讲一些最基础的吧。

概述

树状数组(binary indexed tree),是一种设计新颖的数组结构,它能够高效地获取数组中连续n个数的和。概括说,树状数组通常用于解决以下问题:数组{a}中的元素可能不断地被修改,怎样才能快速地获取一个区间的和?

树状数组基本操作

传统数组(共n个元素)的元素修改和连续元素求和的复杂度分别为O(1)和O(n)。树状数组通过将线性结构转换成伪树状结构(线性结构只能逐个扫描元素,而树状结构可以实现跳跃式扫描),使得修改和求和复杂度均为O(logn),大大提高了整体效率。

假设给定一个序列A,我们设一个数组bits满足bits[i] = A[i–2^k+ 1]+ … +A[i],其中,k为i在二进制下末尾0的个数,i从1开始算!则我们称bits为树状数组。

给定i,如何求2^k? 答案很简单:2^k=i&(i^(i-1)) ,也就是i&(-i),我们就引出了lowbit操作。

lowbit操作:

int lowbit(int x) {
    return x & -x;
}

lowbit的作用是求出最低位的那个1,为什么这样可以求出来呢,是因为计算机中是采用补码的方式来存储数值型数据,所以负数的补码是原码取反再+1,一个二进制数取反后,后面如果现第一个1,那么这位将变为0,而他后面的0都取反为1,所以再+1,就是这位变为1,而他后面的都变为0,这样再与原来的数字进行与运算就能获得.
通过lowbit(x) = x&-x这个操作我们就可以访问到上一层或则下一层,当我们需要去求前3项的和时,只需要向下执行i -= lowbit(i)的操作就行了,从图中你就会发现这正好就是前两项的和再加上第三个和,确实就是如此的巧妙,同理我们更新的时候就是不断向上去更新。因为这里就利用了二进制的巧妙,比如当我们在2的乘法的位置的数就就是前面全部的和然后它再减去它的lowbit就是0,因为他只有最高位是1,而非2的乘方的位置,他会一直向下去寻找,知道找到第一个乘方的位置,加起来就刚好是前n项的和。如下:

数组更新:

void Update(int i, int k) {
    while (i <= n) {
        bits[i] += k;
        i += lowbit(i);
    }
}

计算前缀和:

int PrefixSum(int i) {
    int ans = 0;
    while (i > 0) {
        ans += bits[i];
        i -= lowbit(i);
    }
    return ans;
}

好了,就将那么多了,就让我以三道题结尾,剩下的就自行百度吧。。。

1.单点修改  &&  区间查询

已知一个数列,你需要进行下面两种操作:

1.将某一个数加上x;
2.求出某区间每一个数的和.

题目传送门:[luogu P3374]树状数组 1

#include <bits/stdc++.h>
using namespace std;
long long bits[500005], n; 
int lowbit(int x) {
    return x & -x;
}
void Update(int i, long long k) {
    while (i <= n) {
        bits[i] += k;
        i += lowbit(i);
    }
}
long long PrefixSum(int i) {
    long long ans = 0;
    while (i > 0) {
        ans += bits[i];
        i -= lowbit(i);
    }
    return ans;
}
long long RangeSum(int left, int right) {
    return PrefixSum(right) - PrefixSum(left - 1);
}
int main() {
    int q, judge, delta, left, right;
    scanf("%lld%d", &n, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &delta);
        Update(i, delta);
    }
    while (q--) {
        scanf("%d", &judge);
        if (judge - 1) {
            scanf("%d%d", &left, &right);
            printf("%lld\n", RangeSum(left, right));
        }
        else {
            scanf("%d%d", &judge, &delta);
            Update(judge, delta);
        }
    }
    return 0;
}

2.区间修改  &&  单点查询

已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数数加上x;
2.求出某一个数的值.

对A[]建一个差分数组P[i]=A[i]-A[i-1]
那么A[i]=P[1]+P[2]+…+P[i],这串求和就可以用树状数组实现了,用bits[]建树状数组,实现对P数组的求和,区间修改时,对left~right内每数+delta,可以用Update(left,delta)和Update(right+1,-delta)实现.

题目传送门:[luogu P3368]树状数组 2

#include <bits/stdc++.h>
using namespace std;
long long bits[500005], n; 
int lowbit(int x) {
    return x & -x;
}
void Update(int i, long long k) {
    while (i <= n) {
        bits[i] += k;
        i += lowbit(i);
    }
}
long long PrefixSum(int i) {
    long long ans = 0;
    while (i > 0) {
        ans += bits[i];
        i -= lowbit(i);
    }
    return ans;
}
int main() {
    int q, del = 0, judge, delta, left, right;
    scanf("%lld%d", &n, &q);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &delta);
        Update(i, delta - del);
        del = delta;
    }
    while (q--) {
        scanf("%d", &judge);
        if (judge - 1) {
            scanf("%d", &delta);
            printf("%lld\n", PrefixSum(delta));
        }
        else {
            scanf("%d%d%d", &left, &right, &delta);
            Update(left, delta);
            Update(right + 1, -delta);
        }
    }
    return 0;
}

3.区间修改  &&  区间查询

已知一个数列,你需要进行下面两种操作:

1.将某区间每一个数加上x;
2.求出某区间每一个数的和.

P[]仍为A[]的差分数组,那么
A[1]+A[2]+…+A[n]
=P[1]+(P[1]+P[2])+(P[1]+P[2]+P[3])+…+(P[1]+P[2]+…+P[n])
=n*P[1]+(n-1)*P[2]+(n-3)*P[3]+…+P[n]
=n*(P[1]+P[2]+…+P[n])-(0*p[1]+1*p[2]+…+(n-1)*p[n]).
建树状数组bits1[]、bits2[],分别实现对P[i]和(i-1)*P[i]的求和;
在修改bits1时同时修改bits2,求和时也做相应修改.

题目传送门:[CodeVS 1082]线段树练习 3

#include <bits/stdc++.h>
using namespace std;
long long bits_0[200005], bits_1[200005], n;
int lowbit(int x) {
    return x & -x;
}
void Update(long long *bits, int i, long long k) {
    while (i <= n) {
        bits[i] += k;
        i += lowbit(i);
    }
}
long long PrefixSum(long long *bits, int i) {
    long long ans = 0;
    while (i > 0) {
        ans += bits[i];
        i -= lowbit(i);
    }
    return ans;
}
long long RangeSum(int left, int right) {
    long long ans = PrefixSum(bits_0, right) * right + PrefixSum(bits_1, right);
    return ans - PrefixSum(bits_0, left - 1) * (left - 1) - PrefixSum(bits_1, left - 1);
}
int main() {
    int q, del, judge, delta, left, right;
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &delta);
        Update(bits_1, i, delta);
    }
    scanf("%d", &q);
    while (q--) {
        scanf("%d", &judge);
        if (judge - 1) {
            scanf("%d%d", &left, &right);
            printf("%lld\n", RangeSum(left, right));
        }
        else {
            scanf("%d%d%d", &left, &right, &delta);
            Update(bits_0, left, delta);
            Update(bits_0, right + 1, -delta);
            Update(bits_1, left, 1ll * -delta * (left - 1));
            Update(bits_1, right + 1, 1ll * delta * right);
        }
    }
    return 0;
}