zoukankan      html  css  js  c++  java
  • 树状数组

    树状数组

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

    树状数组的模拟理解可以是这样的,对于树状数组而言你完全可以把它看做是一个辅助的数组。至于它是什么样的辅助数组就和定义有关了。首先,你有一个数组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

  • 相关阅读:
    Div在BOdy中居中
    c_lc_填充每个节点的下一个右侧节点指针 I~II(递归)
    c_pat_哈密顿回路 & 最大点集 & 是否是旅行商路径 & 欧拉路径 & 最深的根(邻接矩阵存图)
    c_lc_二叉搜索树的最近公共祖先 & 二叉树的最近公共祖先(利用性质 | 从p,q开始存储每个结点的父亲)
    c_pat_树题大杂烩(利用性质)
    现在的我,理解了这种「激情」
    b_pat_排成最小的数字 & 月饼(字符串拼接比较a+b<b+a)
    c_lc_二叉搜索树中的众数(中序遍历+延迟更新前驱结点)
    b_pat_分享 & 链表排序 & 链表去重(链表模拟)
    b_pat_弹出序列(栈模拟)
  • 原文地址:https://www.cnblogs.com/pfdm/p/SZSZ.html
Copyright © 2011-2022 走看看