接触到的第一道树状数组的题,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);
}
关于树状数组的求区间和,如果求区间[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;
}