树状数组---数据结构

本文章标记为[ ], 是因为我这里大部分都是从别的博客取来的, 然后进行了整合. 博客地址已经注明.

在POJ上面遇到一道题, 说难并不难, 但其中用到的数据结构却能拓展到某一种问题的求解上, 于是对此作下记录.
题目地址:
    http://poj.org/problem?id=2352
本文主要借鉴:
    关于树状数组实现部分 :http://www.cnblogs.com/Creator/archive/2011/09/10/2173217.html
    关于POJ题目解答部分:  http://blog.sina.com.cn/s/blog_6635898a0100q010.html


以下为正文:
Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of the stars that are not higher and not to the right of the given star. Astronomers want to know the distribution of the levels of the stars.
                                                                                            
For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it's formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3.

You are to write a program that will count the amounts of the stars of each level on a given map.

Input
The first line of the input file contains a number of stars N (1<=N<=15000). The following N lines describe coordinates of stars (two integers X and Y per line separated by a space, 0<=X,Y<=32000). There can be only one star at one point of the plane. Stars are listed in ascending order of Y coordinate. Stars with equal Y coordinates are listed in ascending order of X coordinate.

Output
The output should contain N lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level N-1.

Sample Input

5
1 1
5 1
7 1
3 3
5 5

Sample Output

1
2
1
1
0


题目解析:
题目的大意是, 每个star都有一个level, 计算该level的方法是: 出现在该star左侧(not to the right of the given star)与下方(not higher)的star的个数
    每个star都有一个坐标. input中第一行输入star的个数, 接下来n行输入每个star的坐标. 按照y轴递增顺序输入. 若y相等, 按照x轴递增顺序
    要求输出每个level的star的个数

初看题目,  既然输入是按照顺序的, 那么我们也能在每次输入时与之前的star的x进行比较. 因此只需要考虑坐标 x 的值.
    如果给出一个星星的坐标为(a,b),那么它的等级就等于前面已经输入的x坐标在[0,a]区间的星星数量
    如果直接就开始编码, 不进行数据结构的深入选择的话. 那么以后每次添加一个坐标(x,y)都要计算 [0,x] 中已经存在的坐标数量 n, 令 level[n]++ (即说明该节点属于level[n]). 一旦数据量越来越大, 那么LogN的时间复杂度可能越来越不能被接收.


树状数组:
    那么, 现在提供一种数据结构.
    在一个数组中。若你需要频繁的计算一段区间内的和,你会怎么做?,最最简单的方法就是每次进行计算,但是这需要O(N)的时间复杂度,如这个需求非常的频繁,那么这个操作就会占用大量的CPU时间,进一步想一想,你有可能会想到使用空间换取时间的方法,把每一段区间的值一次记录下来,然后存储在内存中,将时间复杂度降低到O(1),的确,对于目前的这个需求来说,已经能够满足时间复杂度上的要求,尽管带来了线性空间复杂度的提升.

    但若是我们的源数据需要频繁的更改怎么办?使用上面的方案,我们需要大量的更新我们保存到内存中的区间和,而且这中间的很多更新的影响是重叠的,我们需要重复计算。例如对于数组array[10],更新了array[4]值,需要更新区间[4,5],[4,5,6],在更新[4,5,6]需要又一次的计算[4,5],这样的更新带来了非常多的重复计算,为了解决这一问题,树状数组应运而生了。

    当要频繁的对数组元素进行修改,同时又要频繁的查询数组内任一区间元素之和的时候,可以考虑使用树状数组.树状数组是一种非常优雅的数据结构.先来看看一张树状结构的图片
                                                                 

图中C[1]的值等于A[1],C[2]的值等于C[1]+A[2]=A[1]+a[2],C[4]的值=C[2]+C[3]=A[1]+A[2]+A[3]+A[4],假设我们现在需要更改元素a[2],那么它将只影响到得c数组中的元素有c[2],c[4],c[8],我们只需要重新计算这几个值即可,减少了很多重复的操作。这就是树状结构大致的一个存贮示意图,下面看看他的定义:

    假设a[1...N]为原数组,定义c[1...N]为对应的树状数组:
    c[i] = a[i - 2^k + 1] + a[i - 2^k + 2] + ... + a[i] (其中k为i的二进制表示末尾0的个数)

下面根据式子进行举例:
                                

基本操作:
对于C[i]=a[i - 2^k + 1]...a[i]的定义中,比较难以逐磨的k,他的值等于i这个数的二进制表示末尾0的个数.如4的二进制表示0100,此时k就等于2,而实际上我们还会发现2^k就是前一位的权值,即0100中,2^2=4,刚好是前一位数1的权值.所以所以2^k可以表示为n&(n^(n-1))或更简单的n&(-n),例如:
(原博主使用了 n&(-n), 这里因为我无法理解, 也就很难记忆, 所以选择第一种比较复杂的. 因为之前接触过"计算二进制位数中1个个数"题目, 想要将一个二进制数最后一个1去掉的办法是 n&(n-1), 想要获得只有最后一个1的办法就是: n^(n&(n-1)))
为了表示简便,假设现在一个int型为4位,最高位为符号位
int i=3&(3^(3-1));     此时i=1,3的二进制为0011,(3^2)的二进制为0001,所以0011&0001=1
int j=4&(4^3);         此时j=4,理由同上..
所以 计算2^k我们可以用如下代码:
int lowbit(int x)
{
    return x&(x^(x-1));
}


接着, 想要 求 (0, end)的和, 那么:
比如, 若我们需要求sum[1..7]个元素的和,仅需要计算c[7]+c[6]+c[4]的和即可,究竟时间复杂度怎么算呢?一共要进行多少次求和操作呢?
int sum(int i)//求前i项和
{
    int s=0;
    while(i>0)
    {
        s+=c[i];
        i-=lowbit(i); //这一步实际上等价于将i的二进制表示的最后一个1剪去,再向前数当前1的权个数(例子在下面),
                            //而n的二进制里最多有log(n)个1,所以查询效率是log(n)
                            //在示意图上的操作即可理解为依次找到所有的子节点
    }
    return s;
}
以求sum[1..7]为例,二进制为0111,右边第一个1出现在第0位上,也就是说要从a[7]开始向前数1个元素(只有a[7]),即c[7];
然后将这个1舍掉,得到6,二进制表示为0110,右边第一个1出现在第1位上,也就是说要从a[6]开始向前数2个元素(a[6],a[5]),即c[6];
然后舍掉用过的1,得到4,二进制表示为0100,右边第一个1出现在第2位上,也就是说要从a[4]开始向前数4个元素(a[4],a[3],a[2],a[1]),即c[4].
所以s[7]=c[7]+c[6]+c[4]


最后,要 修改数组中的元素:
void add(int i,int val)
{
    while(i<=MAX)
    {
        c[i]+=val;
        i+=lowbit(i); //i+(i的二进制中最后一个1的权值,即2^k),在示意图上的操作即为提升一层,到上一层的节点.
                            //这个过程实际上也只是一个把末尾1后补0的过程(例子在下面)
    }
}

以修改a[2]元素为例,需要修改c[2],2的二进制为0010,末尾补0为0100,即c[4]
4的二进制为0100,在末尾补0为1000即c[8]。所以我们需要修改的有c[2],c[4],c[8]



回到题目:
既然我们已经知道, 每个新添加的star等级就等于前面已经输入的x坐标在[0,a]区间的星星数量, 那么使用树状数组来求解就明显符合了
这样一来, 虽然修改时稍微复杂了些, 但要计算和时就明显简化了

int c[MAX];             //此数组下标即为输入的坐标的x值, c[x]的值即为横坐标为x的star的数量(纵坐标当然不会相同). 已经被初始化为全0
int main(){
    int i, x, y;
    scanf("%d", &n);
     //输入的n对坐标
    for(i = 0; i < n; i ++){
        scanf("%d%d", &x, &y);
                               //sum计算前面已经输入的x坐标在[0,a]区间的星星数量, 复杂度为 logN
                               //level数组初始化全为0, 记录每个level的star数量
                                        //这里也因为是有序输入, 所以每输入一个star坐标都可以确定它的level
        lev[sum(++x)] ++;      // 最好加一, 因为如果输入时x有0出现, 那么使用 n&(-n) 求解的方法就可能超时出问题. 虽然数组的下标每个都加一了, 但不会有什么影响
                                        //记得将新的star记录咋c数组中, 每次修改也都将导致logN的复杂度, 因为以后求sum的值需要这些数据
        add(x, 1);
    }
    for(i = 0; i < n; i ++)
        printf("%d\n", lev[i]);
    return 0;
}

最后, 如果发现问题, 请随时提出
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在据库的选择上面,选择功能强大的Mysql据库进行据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的据有两项,一项就是账号,另一项据就是密码,当管理员正确填写并提交这二者据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值