poj3321解题报告

来源:互联网 发布:华三交换机端口状态 编辑:程序博客网 时间:2024/06/14 05:19
题目大意
给你一颗苹果树,树的主干设为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)) 

[cpp] view plaincopy
  1. inline int Lowbit(int x){  
  2.        // 求x最低位1的权  
  3.        return x & (x^(x-1));  
  4. }  
  5. 定义C[i] = a[i-2^k+1]+a[i-2^k+2]+...+a[i];  
  6. 若要修改 a[k]的值,则C数组的修改过程如下:(n为C数组的大小)  
  7. void Change(int k, int delta){  
  8.        while( k<=n ){  
  9.               C[k] += delta;  
  10.               k += LowBit(k);  
  11.        }  
  12. }  
  13.   
  14. 求a中1..k元素的和  
  15. int Getsum(int k){  
  16.        int ret = 0;  
  17.        while( k>0 ){  
  18.               ret += C[k];  
  19.               k -= Lowbit(k);  
  20.        }  
  21.        return ret;  
  22. }  


 

若要求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的位置。


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

代码:

[cpp] view plaincopy
  1. #include <iostream>  
  2. #include <cstdio>  
  3. using namespace std;  
  4.   
  5. int n, m;  
  6. int inc = 0;  
  7. int num[100001];  
  8. int start[100001];  
  9. int end[100001];  
  10.   
  11.   
  12. //要用到的广义表的结构  
  13. struct TREE   
  14. {  
  15.     int data;  
  16.     TREE * next;  
  17. };  
  18. TREE tree[100001];  
  19.   
  20.   
  21. void dfs(int pos)  
  22. {  
  23.     int i, j, k;  
  24.     start[pos] = ++ inc;  
  25.     TREE *p = tree[pos].next;  
  26.     while (p)  
  27.     {  
  28.         if (start[p->data] == 0)  
  29.         {  
  30.             dfs(p->data);  
  31.         }  
  32.         p = p->next;  
  33.     }  
  34.     end[pos] = inc;  
  35. }  
  36.   
  37. //中间用到的规律值  
  38. int lowBit(int x)  
  39. {  
  40.     return x&(x^(x-1));  
  41. }  
  42. //求从开始到这里的和  
  43. int sSum(int end)  
  44. {  
  45.     int sum = 0;  
  46.     while (end > 0)  
  47.     {  
  48.         sum += num[end];  
  49.         end -= lowBit(end);  
  50.     }  
  51.     return sum;  
  52. }  
  53. //更新自己并且和自己相关的  
  54. void change(int pos, int tmp)  
  55. {  
  56.     while (pos <= n)  
  57.     {  
  58.         num[pos] += tmp;  
  59.         pos += lowBit(pos);  
  60.     }  
  61. }  
  62.   
  63. int main()  
  64. {  
  65.     int j,k,s,t;  
  66.     scanf("%d", &n);  
  67.     for (int i = 1; i < n; i ++)  
  68.     {  
  69.         //每一个点都建一个长长的链表,表示自己的一个分支  
  70.         scanf("%d%d", &s, &t);  
  71.         TREE *p = new TREE;  
  72.         p->data = t;  
  73.         p->next = tree[s].next;  
  74.         tree[s].next = p;  
  75.   
  76.         TREE *q = new TREE;  
  77.         q->data = s;  
  78.         q->next = tree[t].next;  
  79.         tree[t].next = q;  
  80.   
  81.     }  
  82.     //映射到树状数组  
  83.     dfs(1);  
  84.     //每个初始化有一个苹果  
  85.     for (int i = 1; i <= n; i ++)  
  86.     {  
  87.         change(i, 1);  
  88.     }  
  89.     char ch;  
  90.     scanf("%d", &m);  
  91.     for (int i = 0; i < m; i ++)  
  92.     {  
  93.         getchar();  
  94.         scanf("%c%d", &ch, &j);  
  95.         if (ch == 'C')  
  96.         {  
  97.             int sum = sSum(start[j]);  
  98.             sum -= sSum(start[j] - 1);  
  99.             if (sum == 1)  
  100.             {  
  101.                 change(start[j], -1);  
  102.             }  
  103.             else  
  104.             {  
  105.                 change(start[j], 1);  
  106.             }  
  107.         }  
  108.         else  
  109.         {  
  110.             int sum = sSum(end[j]);  
  111.             sum -= sSum(start[j] - 1);  
  112.             printf("%d\n", sum);  
  113.         }  
  114.     }  
  115.     return 0;  
0 0