树状数组

Posted 海蓝笨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组相关的知识,希望对你有一定的参考价值。

当使用前缀和或者差分数组的时候,一般会遇到O(n2)的时间复杂度,此时我们可以使用树状数组来对时间复杂度进行优化。

树状数组主要是利用树形结构来优化我们前缀和或差分数组的计算复杂度使得O(n)的时间复杂度变为O(logn),使用总的时间复杂度减少到O(nlogn).。

构建树状数组的核心是lowbit:

lowbit(x)即x&(-x),效果为得到x最后一个1的位置。比如3&-3等于1表示3的最后一个1在第1位。

-3表示3的补码也就是取反加一。

总的代码如下:

#include <iostream>
using namespace std;
const int N = 1000;
#define lowbit(x) ((x)&-(x))
int tree[N] =  0 ;
void Update(int x, int d) 
    while (x <= N) 
        tree[x] += d;
        x += lowbit(x);
    

int sum(int x) 
    int ans = 0;
    while (x > 0) 
        ans += tree[x];
        x -= lowbit(x);
    
    return ans;

void Print_arr(int* arr,int size) 
    for (int i = 0; i < size; i++) 
        cout << *(arr + i) << " ";
    
    cout << endl;

void Print_sum_arr(int size) 
    for (int i = 0; i <= size; i++) 
        cout << sum(i) << " ";
    

int a[11] =  0,4,5,6,7,8,9,10,11,12,13 ;
int main()


    for (int i = 1; i <= 10; i++) Update(i, a[i]);
    //Print_arr(tree, 11);
    Print_sum_arr(11);
    cout << sum(8) - sum(4) << endl;
    return 0;

 

 (来自<<算法竞赛>>)

对于Update函数的说明:

void Update(int x, int d) //更新所有在树状数组中与x有关的数据,使用lowbit查找
    while (x <= N) 
        tree[x] += d;
        x += lowbit(x);
    

举例说明:对第一个元素更新的时候,1的lowbit为1,那么下一个更新的元素为2,2的lowbit为2,下一个更新4,4的lowbit为8更新8以此类推。

Update之和的,tree里面保存的元素对应上图中的a

 

对于sum函数的说明:

int sum(int x) //求前缀和
    int ans = 0;
    while (x > 0) 
        ans += tree[x];
        x -= lowbit(x);//由于之前是按照+lowbit(x)进行更新的,现在-lowbit(x)得到的位置就是该位置前缀和需要用到的元素。
    
    return ans;

举例说明:对于第8个元素,8的lowbit为8,但由于之前更新1,2,3,4,5,6,7的时候会对8更新,所以tree[8]就是前八个元素之和

举例说明2:对于第7个元素,7的lowbit为1,更新后6的lowbit为2,4的lowbit为3,结束更新。因此前七个元素之和计算公式为tree[7]+tree[6]+tree[4]。

    依次分析:tree[4]表示的是:1,2,3更新过以及4本身,因此tree[4]就是前四个元素之和。

          tree[6]表示的是5更新加上6本身,因此tree[6]是第5、6个之和

举例说明3:对于第6个元素,6的lowbit为2,那么-lowbit之和变为4,在进行-lowbit为0.因此前6个元素之和为tree[6]+tree[4]

    cout << "tree[4]:" << tree[4] << endl;
    cout << "tree[6]:" << tree[6] << endl;
    cout << "sum(6):" << sum(6) << endl;

 

 可以看到sum(6)为tree[4]和tree[6]之和

           

树状数组及二维树状数组

一直以为树状数组能用线段树水过去,直到我今天碰上了树状数组模板题。

然后就是开始认真的学习树状数组,突然发现怎么这么好写qwqqqq。

部分内容转自https://www.cnblogs.com/hsd-/p/6139376.html

一.树状数组

树状数组是一种数据结构,核心思想是利用二进制的补码思想。

首先就是树状数组的结构图

技术分享图片

然后我们对他进行变形

技术分享图片

是不是感觉更好理解了呢?

然后我们对其进行标号

技术分享图片

c数组表示的是记录的值,A数组表示的是原序列。然后就是所有关于树状数组的博客都会有的c数组的存值的展示,博主较懒就不写了,具体看推荐博客。

最重要的性质是

C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)

上面说k是二进制中最低位到最高位的连续零的长度也就是6的二进制是110,那么k就是1,然后我们带入:

c[6]=A[6-2+1]+A[6-2+2]=A[5]+A[6]

看,是不是与上方式子相符,这就是lowbit要实现的功能。那么,lowbit实现的原理是啥呢?

先粘一下lowbit函数的代码

技术分享图片
1 int lowbit(int k)
2 {
3     return k&(-k);
4 }
lowbit

k&(-k)是啥意思呢?

-k是k的补码,也就是反码+1,反码是啥自行百度,反正也很简单。

然后你就会发现,你能够取出最小一位的1,而在上面的例子中就是2,你可以再用几组数据试一下,发现lowbit(i)=2^i。

4(0100),反码4(0011),补码(0100),0100&0100=0100,则lowbit(i)=4,又根据连续0的位数,则k=2,2^2=4,则假设成立。

所以,是不是非常的明白了?

其实还有一种lowbit的写法,k&(k^(k-1)),可以自己手推一下。

然后就是单点修改,代码

技术分享图片
1 void add(int x,int val)
2 {
3     while(x<=n)
4     {
5         tree[x]+=val;
6         x+=lowbit(x);
7     }
8 }
单点修改

为啥是i<=n呢?因为你单点修改是要从叶子结点蹦向父节点的,所以要从小到大。

技术分享图片
 1 int sum(int x)
 2 {
 3     int ans=0;
 4     while(x>0)
 5     {
 6         ans+=tree[x];
 7         x-=lowbit(x);
 8     }
 9     return ans;
10 }
区间查询

那为啥区间查询是i!=0呢?因为区间查询是从父节点蹦向子节点的,所以i要不断减小,记住区间查询是查询的前缀和,所以要查[l,r]的话需要sum(r)-sum(l-1)。

会了以上操作,就先做一下模板题吧 P3374 【模板】树状数组 1

如果我们想区间修改呢?你该不会是枚举然后add(i)吧,不T才怪啊!

这时我们就用到了一种新的思想,差分思想。差分思想是很常见的,下面介绍一下:

数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}

也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];(这个很好证的)。

假如区间[2,4]都加上2的话

a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};

发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.

所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:

b[x]=b[x]+k;b[y+1]=b[y+1]-k;

所以,我们在存的时候就存差分数组,然后我们只改变l,与r+1的值,就好了。

那么单点查询呢?别忘了树状数组存的是前缀和!我们直接sum(a)。

那差分下的区间查询呢?直接原数列a[r]-a[l-1]!

二.二维树状数组

二维树状数组,就是矩阵嘛!你按照矩阵的方式做不就好了!

在add和sum函数里,两层循环,一层y一层x,不就好了!

然后就是二位树状数组所有的代码演示

技术分享图片
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int a[2000][2000],n;
int lowbit(int k)
{
    return k&(-k);
}
void change(int i,int y)
{
    int j;
    while(i<=n)
    {
        j=y;
        while(j<=n)
        {
            a[i][j]++;
            j+=lowbit(j);
        }
        i+=lowbit(i);
    }
}
int ask(int i,int y)
{
    int ans=0;
    int j;
    while(i!=0)
    {
        j=y;
        while(j!=0)
        {
            ans+=a[i][j];
            j-=lowbit(j);
        }
        i-=lowbit(i);
    }
    return ans;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(a,0,sizeof(a));
        int m;
        scanf("%d%d",&n,&m);
        while(m--)
        {
            char opt;
            cin >> opt;
            if(opt==C)
            {
                int x1,x2,y1,y2;
                scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
                change(x2+1,y2+1);
                change(x1,y1);
                change(x1,y2+1);
                change(x2+1,y1);
            }
            else
            {
                int x,y;
                scanf("%d%d",&x,&y);
                printf("%d
",ask(x,y)&1);
            }
        }
        printf("
");
    }
}
二维树状数组

以上是关于树状数组的主要内容,如果未能解决你的问题,请参考以下文章

树状数组的树状数组的经典操作

树状数组模板

数据结构之树状数组从零认识树状数组

树状数组 / 二维树状数组

浅谈树状数组

Bit的树状数组