树状数组

来源:互联网 发布:淘宝网关键词排名查询 编辑:程序博客网 时间:2024/05/17 22:05

poj 3321

题目大意:

给你一颗苹果树,树的主干设为1,每一个分支设为一个数,一直到N,代表这颗苹果树。每个分支上面只能最多有一个苹果,也就是一个枝子上面不可能有两个苹果,另外注意一点,不要把苹果树想象成二叉树,苹果树每个节点可以分出很多叉,应该是多叉树。

 

输入是叉之间的关系,

1 2

1 3

就是主干上面两个叉分别是2 和3.

 

下面是两种操作,Q 和C

C   j  的意思是如果 j 这个枝子上面有苹果就摘下来,如果没有,那么就会长出新的一个

Q  j  就是问 j 这个叉上面的苹果总数。

 

这个题很明显是线段树,其实我很多时候就用树状数组解决,但是,这个数组怎么来定?????算法很容易搞定,但是初始化是问题。

先来说一下树状数组:

 

先看一个例题:
数列操作:
给定一个初始值都为0的序列,动态地修改一些位置上的数字,加上一个数,减去一个数,或者乘上一个数,然后动态地提出问题,问题的形式是求出一段数字的和.

若要维护的序列范围是0..5,先构造下面的一棵线段树:

 

 

 

可以看出,这棵树的构造用二分便可以实现.复杂度是2*N.
每个结点用数组a来表示该结点所表示范围内的数据之和.
修改一个位置上数字的值,就是修改一个叶子结点的值,而当程序由叶子结点返回根节点的同时顺便修改掉路径上的结点的a数组的值. 
对于询问的回答,可以直接查找i..j范围内的值,遇到分叉时就兵分两路,最后在合起来.也可以先找出0..i-1的值和0..j的值,两个值减一减就行了.后者的实际操作次数比前者小一些.
这样修改与维护的复杂度是logN.询问的复杂度也是logN,对于M次询问,复杂度是MlogN.
然而不难发现,线段树的编程复杂度大,空间复杂度大,时间效率也不高.

更好的解决办法就是用树状数组!

下图中的C数组就是树状数组,a数组是原数组

 

 

 

 

对于序列a,我们设一个数组C定义C[i] = a[i-2^k+1] + a[i-2^k+2] + … + a[i],k为i在二进制下末尾0的个数。
2^k的计算可以这样:2^k = i & (i^(i-1)) 

inline int Lowbit(int x){       // 求x最低位1的权       return x & (x^(x-1));}定义C[i] = a[i-2^k+1]+a[i-2^k+2]+...+a[i];若要修改 a[k]的值,则C数组的修改过程如下:(n为C数组的大小)void Change(int k, int delta){       while( k<=n ){              C[k] += delta;              k += LowBit(k);       }}求a中1..k元素的和int Getsum(int k){       int ret = 0;       while( k>0 ){              ret += C[k];              k -= Lowbit(k);       }       return ret;}


 

若要求i..j的元素和的值,则求出 1~i-1和1~j的值,相减即可

在很多的情况下,线段树都可以用树状数组实现.凡是能用树状数组的一定能用线段树.当题目不满足减法原则的时候,就只能用线段树,不能用树状数组.例如数列操作如果让我们求出一段数字中最大或者最小的数字,就不能用树状数组了.

注:用数组保存静态一维线段数也可以不记录左右孩子的下标
如0号是根节点,其孩子是1、2;
1号的孩子是3、4;
2号的孩子是5、6;
3号的孩子是7、8;
……
n号的孩子是2*n+1、2*n+2。

本题如何用树状数组来解决:

 

想必大家都明白上面说的了吧,就是为了快速的查询一个数组某一段的和,一旦这一段经过修改,依然快速给出答案。

而这个题不正是这个意思么,原先都是一个苹果,摘掉或者新长出,不就是改变数组的某个值么,这时候要查询的不就是某个区间的值么?

 

而上面的例子就是一个一维数组,但是现在如何把一个树转化为一维数组呢?这是这道题目的关键,否则没法做。

如果理解了这一点,就好办了。


对一棵树进行深搜,然后将深搜的顺序重新标上号,然后放到一个数组中,记下每一个枝子得起始点和终结点,然后就可以用树状数组了。

还是举个例子吧。。。


如果一棵苹果树是这样的。

 

那么我们知道1是主干。。。。

深搜的顺序是1 4 6 5 7 2 3 当然这也是根据题目的输入建的广义表。

那么当我们深搜的时候就可以在下面标上:

1 4 6 5 7 2 3 (广义表里面的东西,深搜结果)

1 2 3 4 5 6 7(num数组我们求和要用的)

这样不就是树状数组了么。

我们用start【1】= 1 end【1】=7代表1以上的树枝为1-7,也就是所有

用 start【2】= 6  end【2】 = 7 代表2以上的树枝是 3

同理

start【4】=2 end【4】 = 5 这样我们就知道4上面的树枝了

每一个树枝在num【n】总数组中的位置,这样我们就可以计算啦。。。树状数组,和上面将的一样了,成功转换

具体键代码,main函数主要建广义表,dfs主要是找到每一个树枝在num【n】数组中的起点和终点并记录在start和end数组中。


这样下面就是树状数组的三个函数。

有人会问,为什么要+ 或者-  x & (x^(x-1)),这个我们可以看上面的规律,具体起源于什么,我也不知道,根据上面讲的就是这个数的二进制从右边第一个1的位置。


如果还不是很明白,请回复,我会的尽量解释,谢谢。

代码:

#include <iostream>#include <cstdio>using namespace std;int n, m;int inc = 0;int num[100001];int start[100001];int end[100001];//要用到的广义表的结构struct TREE {int data;TREE * next;};TREE tree[100001];void dfs(int pos){int i, j, k;start[pos] = ++ inc;TREE *p = tree[pos].next;while (p){if (start[p->data] == 0){dfs(p->data);}p = p->next;}end[pos] = inc;}//中间用到的规律值int lowBit(int x){return x&(x^(x-1));}//求从开始到这里的和int sSum(int end){int sum = 0;while (end > 0){sum += num[end];end -= lowBit(end);}return sum;}//更新自己并且和自己相关的void change(int pos, int tmp){while (pos <= n){num[pos] += tmp;pos += lowBit(pos);}}int main(){int j,k,s,t;scanf("%d", &n);for (int i = 1; i < n; i ++){//每一个点都建一个长长的链表,表示自己的一个分支scanf("%d%d", &s, &t);TREE *p = new TREE;p->data = t;p->next = tree[s].next;tree[s].next = p;TREE *q = new TREE;q->data = s;q->next = tree[t].next;tree[t].next = q;}//映射到树状数组dfs(1);//每个初始化有一个苹果for (int i = 1; i <= n; i ++){change(i, 1);}char ch;scanf("%d", &m);for (int i = 0; i < m; i ++){getchar();scanf("%c%d", &ch, &j);if (ch == 'C'){int sum = sSum(start[j]);sum -= sSum(start[j] - 1);if (sum == 1){change(start[j], -1);}else{change(start[j], 1);}}else{int sum = sSum(end[j]);sum -= sSum(start[j] - 1);printf("%d\n", sum);}}return 0;}




生气

0 0
原创粉丝点击