题目大意:
给你一颗苹果树,树的主干设为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;
}