树状数组

树状数组

树状数组具体的定义:查看百度百科

树状数组的模拟理解可以是这样的,对于树状数组而言你完全可以把它看做是一个辅助的数组。至于它是什么样的辅助数组就和定义有关了。首先,你有一个数组A[100]里面存放着相应的数据,而这时又定义了一个辅助数组C[100],C[100]的作用就是记录A数组中某几个元素的和。而对于树状数组,我们规定C[1]存的是A[1],C[2]存的是A[1]+A[2]…如下图:

 

既然我们已经知道C数组仅仅是起到辅助作用,那么我们在思考的时候完全不必想其构造过程,只需要懂得构造方法和C数组究竟代表什么即可。

对于树状数组的算法一般都会有看到一个函数是:lowbit()函数,可以查看博客探究它的含义。现在只需要知道它是用来构造辅助数组C的就可以了。

int lowbit(int x)

{

    return x&(-x);

}

紧接着会是这样两个函数:

int sum(int x)

{

    int result=0;

    while(x>0)

    {

        result+=c[x];

        x-=lowbit(x);

    }

    return result;

}

void add(int x,int n)

{

    while(x<MAX)

    {

        c[x]+=n;

        x+=lowbit(x);

    }

}

在这里你需要明确的就是这两个函数都和辅助数组C有关,但是实质是获取A的信息(因为C是辅助数组,用在存A中的信息的)。第一个函数是表示获取数组A[1]到A[x]的和。

第二个函数则是在A数组中的第x位置上加上个数n。而我们在程序使用中并没有定义数组A(没必要浪费空间)原因是我们所有的操作都是在C(可以想象存在一个A,然后C它的辅助数组)上进行的,通过C来获取信息。

这样基本上就算对树状数组有所了解了。有些人肯定会很疑问,不明白如果没有定义A如何构造C呢?其实这就归结于第二个函数add()  我们可以利用add()去构造C.

例如:

有一串数据  1  2  3  4

我们想把他们存到A中(A[1]=1,A[2]=2,A[3]=3,A[4]=4)这里面没有用A[0]是因为lowbit()函数,所以树状数组使用应注意不能用0号空间。

这时我们只需要

for(i=1;i<=n;i++)

{

   int temp=0;

   cin>>temp;

   add(i,temp);

}

可以理解为每次输入一个值,我就把它存到数组A的对应空间中,而数组A并不是真实存在的。但是数组C存在且add是针对数组C进行操作的。这样就利用了add构造了虚拟的数组A同时也构造了相应的辅助数组C。

而我们想获得A数组前N项和的时候只需要sum(N)即可。(一定要理解好A和C的关系)

树状数组使用变型1:

上面已经介绍了关于构造数组C的函数,接下来就对同一个函数进行用法上的改变。

对于add函数来说:

for(i=1;i<=n;i++)

{

   int temp=0;

   cin>>temp;

   add(temp,1);

}

这样的写法会令初学者困扰,而仔细看就很容易发现。其实就是输入一个数我们就以这个数为索引即:A[temp],让它的值为1。说白了就是把A数组用来计数,输入1我就在1号空间把其值变成1,输入1000就在1000号空间变1。而C数组的本质还没有改变,仍然指的是A某几个单元的和。仍然是辅助数组。

树状数组使用变型2:

第二种变型是涉及到两个函数add和sum的,这时你就需要去了解下add和sum函数的内部结构,和C数组的构造过程了,而不能单单关注他们的含义了。含义只是使其思考及其使用上更加方便,但实质还需要了解的。

下面给出两个函数的变型:

int sum(int index)

{

         int result=0;

         while(index<MAX)//注意此处不同

         {

                 result+=c[index];

                 index+=lowbit(index); //注意此处不同

         }

         return result;

}

void add(int index, int number)

{

         while(index>0) //注意此处不同

         {

                 c[index]+=number;

                 index-=lowbit(index); //注意此处不同

         }

}

如果两个函数进行了如上的更改,则其含义已然改变。此时add指的是将数组A的前N项都加上number。即:add(n,number);

如:add(3,1)就是指将A数组的前三个空间都加1;

则此时的C也并不代表是A的某几项元素之和了!因为他的构造的函数add已经发生了改变,这时C存的就是对应A的值。而sum函数就是返回当前索引下A数组中的值(也就是C数组中的值)

如:C[3]=A[3]=1;则sum(3)的值就是1

离散化

 

对于数组变型1而言,我们曾用数组进行计数,输入100则就令A[100]=1。然而当我们输入的数比较大例如1000000000,那我们就没必要开那么大的数组了。例如如下一串数:

1000 999 998

我们想求998之前比998大的数,也就是998的逆序数。那么我们没必要开1000的数组去存三个数,而是将其进行离散化为 3 2 1即可。这样一来他们之间的大小关系和前后顺序没有受到任何影响,同时只需要开3个空间大小的数组即可。

思路很简单,具体代码如下:

struct data

{

   int index;//输入的顺序的索引 第几个输入的就是几

   int value;//输入的值

}a[MAX];

for(i=1;i<=n;i++)

{

    scanf("%d",&a[i].value);//获得值

    a[i].index=i;//获得索引

}

sort(a+1,a+n+1,cmpv);//利用值进行排序,显然先后顺序乱了

for(i=1;i<=n;i++)//利用索引将其离散化,并还原成原来的输入顺序

{

    b[a[i].index]=i;

}

对应习题分别为HDU 2492 POJ 2352 HDU 1556 POJ 2299

原文地址:https://www.cnblogs.com/pfdm/p/SZSZ.html