线段树入门

来源:互联网 发布:上古世纪捏脸萝莉数据 编辑:程序博客网 时间:2024/05/22 00:51

转自 http://hi.baidu.com/semluhiigubbqvq/item/be736a33a8864789f4e4ad18

把问题简化一下:

在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个询问,每个询问输入一个点,要求这个点在多少条线段上出现过;

最基本的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中;

每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n)

这道题m和n都是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时

-----

因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费;

线段树就是可以解决这类问题的数据结构

举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次

在[0,7]区间上建立一棵满二叉树:(为了和已知线段区别,用【】表示线段树中的线段)

                                               【0,7】
                               /                                            \
                     【0,3】                                           【4,7】
                  /               \                                    /                \
       【0,1】             【2,3】                 【4,5】               【6,7】
         /      \                 /      \                     /      \                   /      \
【0,0】 【1,1】【2,2】 【3,3】   【4,4】 【5,5】 【6,6】 【7,7】

每个节点用结构体:

struct line
{
      int left,right;//左端点、右端点
      int n;//记录这条线段出现了多少次,默认为0
}a[16];

和堆类似,满二叉树的性质决定a[i]的左儿子是a[2*i]、右儿子是a[2*i+1];

然后对于已知的线段依次进行插入操作:

从树根开始调用递归函数insert

void insert(int s,int t,int step)//要插入的线段的左端点和右端点、以及当前线段树中的某条线段
{
      if (s==a[step].left && t==a[step].right)
      {
            a[step].n++;//插入的线段匹配则此条线段的记录+1
            return;//插入结束返回
      }
      if (a[step].left==a[step].right)   return;//当前线段树的线段没有儿子,插入结束返回
      int mid=(a[step].left+a[step].right)/2;
      if (mid>=t)    insert(s,t,step*2);//如果中点在t的右边,则应该插入到左儿子
      else if (mid<s)    insert(s,t,step*2+1);//如果中点在s的左边,则应该插入到右儿子
      else//否则,中点一定在s和t之间,把待插线段分成两半分别插到左右儿子里面
      {
            insert(s,mid,step*2);
            insert(mid+1,t,step*2+1);
      }
}

三条已知线段插入过程:

[2,5]

--[2,5]与【0,7】比较,分成两部分:[2,3]插到左儿子【0,3】,[4,5]插到右儿子【4,7】

--[2,3]与【0,3】比较,插到右儿子【2,3】;[4,5]和【4,7】比较,插到左儿子【4,5】

--[2,3]与【2,3】匹配,【2,3】记录+1;[4,5]与【4,5】匹配,【4,5】记录+1

[4,6]

--[4,6]与【0,7】比较,插到右儿子【4,7】

--[4,6]与【4,7】比较,分成两部分,[4,5]插到左儿子【4,5】;[6,6]插到右儿子【6,7】

--[4,5]与【4,5】匹配,【4,5】记录+1;[6,6]与【6,7】比较,插到左儿子【6,6】

--[6,6]与【6,6】匹配,【6,6】记录+1

[0,7]

--[0,7]与【0,7】匹配,【0,7】记录+1

插入过程结束,线段树上的记录如下(红色数字为每条线段的记录n):

                                               【0,7】
                                                    1
                               /                                            \
                     【0,3】                                           【4,7】
                         0                                                     0
                 /                 \                                     /                 \
       【0,1】                 【2,3】                【4,5】                【6,7】
            0                           1                          2                         0
          /    \                      /      \                     /     \                    /      \
【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】
     0            0            0            0            0            0           1           0

询问操作和插入操作类似,也是递归过程,略

2——依次把【0,7】 【0,3】 【2,3】 【2,2】的记录n加起来,结果为2

4——依次把【0,7】 【4,7】 【4,5】 【4,4】的记录n加起来,结果为3

7——依次把【0,7】 【4,7】 【6,7】 【7,7】的记录n加起来,结果为1

不管是插入操作还是查询操作,每次操作的执行次数仅为树的深度——logN

建树有n次插入操作,n*logN,一次查询要logN,m次就是m*logN;总共复杂度O(n+m)*logN,这道题N不超过30000,logN约等于14,所以计算量在10^5~10^6之间,比普通方法快了1000倍;

这道题是线段树最基本的操作,只用到了插入和查找;删除操作和插入类似,扩展功能的还有测度、连续段数等等,在N数据范围很大的时候,依然可以用离散化的方法建树。

湖大的那道题目绕了个小弯子,alpc12有详细的题目和解题报告

转自 http://www.cppblog.com/sicheng/archive/2008/01/09/40791.html

HOJ 11107

Number of stonesTime Limit: 3000ms, Special Time Limit:7500ms,Memory Limit:32768KBTotal submit users: 13, Accepted users:1Problem 11107 : No special judgementProblem descriptionThere are N baskets rounded in a circle, and numbered as 1、2、3、…….、N-1、N, in clockwise. At the beginning, all of the baskets are empty. Some workers go to the moutain to collect stones. When they are back,they put their stones to some baskets. The workers have a habit, Once a worker come back, he choose a baskets, and choose a direction(clockwise or anticlockwise), he put one stone to this basket and move to the next basket according to the direction he has chosen, he continues doing this until all of the stones they have collected have been put down.
Sometimes the workers ask you how many stone it is in the basket, as there are too many baskets, You are to wirte a program to calculate this.
InputThe input test file will contain multiple cases. Each test case:
In the first line of the input contain two integers N,M(3 <= N <= 100000, 0 <= M <= 300000). Following M lines,each line represents an event. There are only three kinds of events: Q, C, U. And the format is:
“Q x”, query the number of stones in the basket x.
“C x num”, a worker comes back and the number of the stones he has picked up is num, he puts down stones from the basket x in clockwise.
“U x num”, a worker comes back and the number of the stone he has picked up is num, he puts down stones from the basket x in anticlockwise.
(x, num are both integers, 1 <= x <= N, 1 <= num <= 10000)
OutputFor each query “Q x”, print the current number of stones in basket x.Sample Input
5 8            C 5 8            Q 5            Q 4            U 5 3            C 2 6            Q 2            Q 1            U 2 8            
Sample Output
2            1            4            3            
Problem Sourcebirdman
 1/**//*
 2 * 主程序要作的事情
 3 * 1.确定N :必须是2^n,可以取实际n的上界
 4 * 2.build(left, right);
 5 *
 6 */

 7
 8#include <cstdio>
 9#include <cstring>
10
11const int N = 131072;                //必须是2^n,可以取实际n的上界
12
13int upperbound;
14
15struct Node {
16    int i, j, c, m;                    //left, right
17}
 T[N*2];
18
19void bd(int d, int left, int right) {
20    T[d].i = left, T[d].j = right, T[d].c = 0;
21    if(right > left) {
22        bd(d*2+1, left, T[d].m = (left+right)>>1);
23        bd(d*2+2, T[d].m+1, right);
24    }

25}

26
27void build(int left, int right) {
28    upperbound = 1;
29    while(upperbound < right-left+1) upperbound <<= 1;
30    bd(0, left, left + upperbound-1);
31}

32
33void add(int d, int left, int right, int c) {
34    if(left <= T[d].i && right >= T[d].j) {
35        T[d].c += c;
36    }

37    else {
38        if(left <= T[d].m) add(d*2+1, left, right, c);
39        if(right > T[d].m) add(d*2+2, left, right, c);
40    }

41}

42
43int get(int x) // 获得点的覆盖次数
44    int idx = upperbound+x-1, sum = 0;
45    do {
46        sum += T[idx].c;
47        idx = (idx-1)>>1;
48    }
 while(idx != 0);
49    return sum;
50}

51
52int n, m;
53
54void Add(int x, int num) {
55    int laps = (num-(n-x))/n;
56    if(laps > 0{
57        add(00, n-1, laps);
58    }

59    num -= laps*n;
60    if(n->= num) {
61        add(0, x, x+num-11);
62    }

63    else {
64        add(0, x, n-11);
65        add(00, (x+num-1)%n, 1);
66    }

67}

68
69int main() {
70    while(scanf("%d %d"&n, &m) != EOF) {
71        build(0, n-1);
72        while(m--{
73            char cmd;
74            int x, num;
75            scanf(" %c"&cmd);
76            if(cmd == 'Q'{
77                scanf("%d"&x); 
78                --x;
79                printf("%d\n", get(x));
80            }

81            else if(cmd == 'C'{
82                scanf("%d %d"&x, &num);
83                --x;
84                Add(x, num);
85            }

86            else if(cmd == 'U'{
87                scanf("%d %d"&x, &num);
88                --x;
89                int y = (x-num+1% n;
90                if(y < 0) y += n;
91                Add(y, num);
92            }

93        }

94    }

95
96    return 0;
97}