POJ 2352(顺路讲解一下树状数组)

接触到的第一道树状数组的题,AC之后感觉对树状数组思想的理解明显清晰了很多,入门必备呀。

先来讲讲树状数组吧。

上个图


如果要求区间和,例如求a3->a8,我们先用数组c来记录下来各个区间的和,那么就可以直接c8-c2就可以得到a3->a8的区间和了。

c[n]数组的下标是用来记录数组a[1]->a[n]的区间和。

再想,我们如果要求s->t的和可以怎么求?

是不是可以用(1->t)减去(1->s-1)?

同理,树状数组也可以用这种方式求和。比如求a[2]的话就可以用c[2]-c[1]来得到a[2]。

关于c数组的记录方法,就不得不提到x&(-x)这个表达式。

x&(-x)是用来计算每次查询或者更新时候c数组的偏移量。

通常写成函数的形式。

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


注意:计算机中负数是用的补码表示的,正数是用的原码表示,可以自己拿张草稿纸来实现一下x&(-x)的计算


关于树状数组的求区间和,如果求区间[1,8],那么直接c[8]就好,但是如果要求区间[4,8]该怎么办?

那么可以用区间[1,8]的和减去区间[1,3]的和就得到了区间[4,8]的和。

那又如何求区间[1,3]呢?我们可以c[2]+c[3]就得到了区间[1,3]的和了。

下面是求区间[1,x]和的函数:

int sum(int x)
{
    int s = 0;
    while (x > 0)
    {
        s += c[x];
        x -= lowbit(x);
    }
    return s;
}

以求区间[1,3]为例,首先x等于3代入方程,3 > 0,则s = c[3],然后执行x-=lowbit(x);

让我们进入lowbit函数,首先3的二进制原码为0000 0011,-3为3的补码,则-3的二进制码为1111 1101,进行&运算之后为1,所以x-=1,此时x为2。由于2>0,所以s此时等于c[3] + c[2]。执行x-=lowbit(x)之后x = 0。循环结束,此时s已经是区间[1,3]的和了。

我们再来看树状数组的更新函数:

int update(int x, int num)
{
    while (x <= MAX)
    {
        c[x] += num;
        x += lowbit(x);
    }
}

和线段树一样,树状数组也是需要对节点所影响到的所有节点进行更新,采取从根到顶的方式。也就是对每个影响到的节点都加上更改信息。

树状数组时间复杂度O(log n)

————————————————————————分割线——————————————————————————————

题意:有n个星星节点,存在星星节点左下角(包括正左和正下)的其他星星节点,则该星星节点比它左下角的星星节点大,level 0表示该星星节点没有比他还小的节点,level 1表示存在一个比该星星节点小的点。输出统计好的每个level等级存在多少星星节点。




解题思路:为什么要用树状数组呢,因为如果你用for循环统计的话,由于数据很大然后又有很多组数据需要统计,那么肯定是会超时的,所以此时需要一种高效的数据结构(感觉像一句废话TAT),树状数组类似于线段树,能够很高效的解决区间问题,将这道题提炼一下其实也就是一个统计区间和的问题,线段树写起来好麻烦的=。=于是乎用了树状数组。

直接上代码:

/*因为是按照y升序输入,所以后面输入对前面输入并无影响
**前x与后x如果相同,那么肯定后x是包含前x的,因为是按照y升序
**即前后x虽是在同一列,但后x肯定在前x上面,即包含前x
**PS:树状数组下标从1开始
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int level[32001];
int c[32001];
int lowbit(int x)
{
    return x & (-x);
}
int sum(int x)
{
    int s = 0;
    while (x > 0)
    {
        s += c[x];
        x -= lowbit(x);
    }
    return s;
}
int update(int x)
{
    while (x <= 32001)
    {
        c[x]++;
        x += lowbit(x);
    }
}
int main()
{
    int n;
    int x, y;
    while(~scanf("%d", &n))
    {
        int N = n;
        memset(level, 0, sizeof(level));
        memset(c, 0, sizeof(c));
        while (n--)
        {
            scanf("%d %d", &x, &y);
            level[sum(x+1)]++;
            update(x+1);
        }
        for (int i = 0; i <= N - 1; i++)
            printf("%d\n", level[i]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值