线段树初探

一.基础准备

 
        自己发现:n个点的话,共n-1个非叶子节点,不过没证明过,直接看图。

image

        线段树,也叫区间树(有人说实际不一样),是一个完全二叉树,它在各个节点保存一条线段,因而常用于解决数列维护问题,树的根节点表示是“整体”的区间,左右子树分别表示这个区间的左半边和右半边。

        线段树是一个满二叉树,假设共n个节点,区间为[0,n-1],那么共2*n-1个节点,深度log(2*n-1),即O(logn),这基本上述所有操作的复杂度。

        线段树缺点:数列中的数不能添加或者删除。

        查询的时候为什么也需要hidden(root)操作?看区间修改,估计是因为当用户修改一个区间的值时,如果连同其子孙全部修改,则改动的节点数必定会远远超过O(log n)个。因而,如果要想把区间修改操作也控制在O(log n)的时间内,只修改O(log n)个节点的信息就成为必要。然后如果要用到区间的子区间的时候,我们才去真正更新!

        hidden(root)里为什么不更新id本身和?因为add函数最后一行已经更新。 

        hidden函数里st.node[root<<1].delta += st.node[root].delta为什么连加呢?因为可能上次查询没执行到改点,而且由于更新并未更新子节点,所以连加。

        以上总结纯属个人观点,如有错误,还请指正。

二.Java实现

        以POJ3468为例,直接那我的代码去AC吧。

  1: /*
  2:  * 1.我知道错误在哪啦,节点编号不能从0开始,否则左子树编号始终是0
  3:  * 2.root<<1 +1 前面必须加括号,通过打印节点发现全是2的幂
  4:  * 3.增量必须是long,否则wa
  5:  */
  6: import java.io.InputStreamReader;
  7: import java.util.Scanner;
  8: 
  9: /*
 10:  * n个点的话,共n-1个非叶子节点,
 11:  * 我认为这个很重要,
 12:  */
 13: public class Main {
 14: 
 15:   static int N = 400000;
 16:   static Node node[] = new Node[N];
 17:   
 18:   public static void main(String[] args) {
 19:     //必须放在main内
 20:     for(int i=0; i<N; i++) {
 21:       node[i] = new Node();
 22:     }
 23:     new ST().go();
 24:   }
 25: }
 26: 
 27: class Node {
 28:   int left;
 29:   int right;
 30:   long sum;
 31:   long delta;
 32:   
 33:   public Node() {
 34:     this.delta = 0;
 35:     this.sum = 0;
 36:   }
 37: }
 38: 
 39: class ST {
 40:   String str;//判断是询问还是增加
 41:   int a,b,c;
 42:   long ans = -1;
 43:   Scanner sc = new Scanner(new InputStreamReader(System.in));
 44:   int n,m;//n个数据,m次询问
 45:   int array[];
 46:   Main st = new Main();//为了用node数组
 47:   
 48:   public void go() {
 49:     n = sc.nextInt();
 50:     m = sc.nextInt();
 51:     array = new int[n+1];
 52:     for(int i=1; i<n+1; i++) {
 53:       array[i] = sc.nextInt();
 54:     }
 55:     buildTree(1,array,1,n);
 56:     //printTree(1);
 57:     for(int i=0; i<m; i++) {
 58:       str = sc.next();
 59:       a = sc.nextInt();
 60:       b = sc.nextInt();
 61:       
 62:       if(str.equals("Q")) {
 63:         ans = query(1,a,b);
 64:         System.out.println(ans);
 65:       }else {
 66:         c = sc.nextInt();
 67:         //System.out.println(a+" "+b+" "+c);
 68:         add(1,a,b,c);
 69:       }
 70:     }
 71:   }
 72: 
 73:   private void buildTree(int root, int[] array,int left,int right) {
 74:     st.node[root].left = left;
 75:     st.node[root].right = right;
 76:     if(left==right) {
 77:       st.node[root].sum = array[left];
 78:       st.node[root].delta = 0;
 79:       return ;
 80:     }
 81:     int mid = (left+right)>>1;
 82:     //注意:中值在左子树
 83:     buildTree(root<<1, array, left, mid);
 84:     buildTree((root<<1)+1, array, mid+1, right);
 85:     //下面这句别忘了,就是更新父节点的值,注意不是连加
 86:     st.node[root].sum = st.node[root<<1].sum + st.node[(root<<1)+1].sum;
 87:   }
 88: 
 89:   private long query(int root, int a, int b) {
 90:     /*
 91:      * n个点的话,共n-1个非叶子节点,所以传过来的
 92:      * 范围可直接与节点编号比较
 93:      * 笔者认为理解这点很重要
 94:      */
 95:     //System.out.println(root);
 96:     if(st.node[root].left==a&&st.node[root].right==b) {
 97:       return st.node[root].sum;
 98:     }
 99:     hidden(root);
100:     int mid = (st.node[root].left+st.node[root].right)>>1;
101:     if(b<=mid) {//有等号,因为建树时mid在左子树
102:       return query(root<<1, a, b);
103:     }else if(a>mid) {
104:       return query((root<<1)+1, a, b);
105:     }else {
106:       return query(root<<1, a, mid) + query((root<<1)+1, mid+1, b);
107:     }
108:   }
109: 
110:   //将更新标记传递至子节点
111:   private void hidden(int root) {
112:     if(st.node[root].delta!=0) {
113:       st.node[root<<1].delta += st.node[root].delta;
114:       st.node[root<<1].sum += st.node[root].delta
115:           *(st.node[root<<1].right - st.node[root<<1].left + 1);
116:       st.node[(root<<1)+1].delta += st.node[root].delta;
117:       st.node[(root<<1)+1].sum += st.node[root].delta
118:           *(st.node[(root<<1)+1].right - st.node[(root<<1)+1].left + 1);
119:       //别忘了加上,否则栈溢出
120:       st.node[root].delta = 0;
121:     }
122:   }
123:   private void printTree(int i) {
124:     if (st.node[i].left != 0) {
125:       printTree(2 * i);
126:       System.out.print(i+":"+"[" + st.node[i].left + "," + st.node[i].right + "] ");
127:       printTree(2 * i + 1);
128:     }
129:   }
130:   private void add(int root, int a, int b, int c) {
131:     if (st.node[root].left == a && st.node[root].right == b) {// 找到操作区间了
132:       st.node[root].delta += c; // 标记
133:       st.node[root].sum += c * (b - a + 1);
134:       return;
135:     }
136:     hidden(root);
137:     int mid = (st.node[root].left + st.node[root].right)>>1;
138:     if (b <= mid) {
139:       add(root * 2, a, b, c);
140:     } else if (a > mid) {
141:       add(root * 2 + 1, a, b, c);
142:     } else {
143:       add(root * 2, a, mid, c);
144:       add(root * 2 + 1, mid + 1, b, c);
145:     }
146:     st.node[root].sum = st.node[root * 2].sum + st.node[2 * root + 1].sum;// 更新父节点
147:   }
148: }
posted @ 2013-08-07 09:56  加拿大小哥哥  阅读(970)  评论(0编辑  收藏  举报