线段树简析

线段树资料:http://dongxicheng.org/structure/segment-tree/

PS: 以HDU1166为例

线段树的使用一般分如下几个操作

  1. 建树
  2. 插入
  3. 查询
  • 建树

  建树的操作类似与构建一个二叉树,是一个递归的过程

void build(int l, int r, int step)    

{

    tree[step].left = l;

    tree[step].right = r;

    tree[step].add = 0;

    if (l == r)        //到达叶子节点了

    {

        tree[step].num = a[l];

        return;

    }

    int mid = (l + r) >> 1;

    build(l, mid, L(step));

    build(mid+1, r, R(step));

    //其构造是自下而上(先求出其子节点的值),那么其父亲节点很明显就是其左右节点值的和

    tree[step].num = tree[L(step)].num + tree[R(step)].num;

}
  • 插入

  在建立好之后的线段树中,若要是修改一段区间的值时如:[l, r]  代码如下

void insert(int l, int r, int step, int add)

{

    if (tree[step].left == tree[step].right)

    {

        tree[step].num += add;

        return;

    }

    int mid = (tree[step].left + tree[step].right) >> 1;

    if (mid >= l) 

        insert(l, r, L(step), add);

    if (mid < r)

        insert(l, r, R(step), add);

    //由于可能其子节点有更新,我们需要在重新计算一次step这层的值

    tree[step].num = tree[L(step)].num + tree[R(step)].num;

}

  结果正确,但是会超时,因此在这里我们添加了一个标志位,当到达某一层的时候,我们判断一下该层的标志位是否有值,若是有,则修改其左右节点的值,其他的子节点暂时不管,(其实在有些情况下可能根本就不需要用到其子节点的值)代码如下

void update(int step)    //若当前节点的标志位add有值,则更新其左右节点

{

    tree[L(step)].num += tree[step].add * (tree[L(step)].right - tree[L(step)].left + 1);

    tree[L(step)].add += tree[step].add;    //将标志位传给子节点

    tree[R(step)].num += tree[step].add * (tree[R(step)].right - tree[R(step)].left + 1);

    tree[R(step)].add += tree[step].add;

    tree[step].add = 0;                        //父节点清0

}

其完整代码如下

void insert(int l, int r, int step, int add)

{

    if (l<= tree[step].left && tree[step].right <= r)

    {

        tree[step].add += add;    

        tree[step].num += add * (tree[step].right- tree[step].left+ 1);    //add * len

        return;

    }

    if (tree[step].add != 0)    //

    {

        update(step);

    }

    int mid = (tree[step].left + tree[step].right) >> 1;

    if (mid >= l) 

        insert(l, r, L(step), add);

    if (mid < r)

        insert(l, r, R(step), add);

    //由于可能其子节点有更新,我们需要在重新计算一次step这层的值

    tree[step].num = tree[L(step)].num + tree[R(step)].num;

}
  • 查询

  查询的过程和建立有点相似,只是需要返回每一层的结果即可,注意:在查询的时候我们也需要判断其标志位

int query(int l, int r, int step)

{

    int ans = 0;

    if (l <= tree[step].left && tree[step].right <= r)

    {

        return tree[step].num;

    }

    if (tree[step].add != 0)

    {

        update(step);

    }

    int mid = (tree[step].left + tree[step].right) >> 1;

    if (mid >= l)    //与左节点有交集

        ans += query(l, r, L(step));

    if (mid < r)    //与右节点有交集

        ans += query(l, r, R(step));

    return ans;

}

附上HDU1166完整代码如下:

/*

    HDU1166

*/

#include <iostream>

using namespace std;

const int N = 50005;



struct node

{

    int left, right;

    int  num, add;

}tree[4*N];



int L(int n)

{

    return n<<1;

}

int R (int n)

{

    return (n<<1) | 1;

}

void update(int step)    //若当前节点的标志位add有值,则更新其左右节点

{

    tree[L(step)].num += tree[step].add * (tree[L(step)].right- tree[L(step)].left + 1);

    tree[L(step)].add += tree[step].add;    //将标志位传给子节点

    tree[R(step)].num += tree[step].add * (tree[R(step)].right - tree[R(step)].left + 1);

    tree[R(step)].add += tree[step].add;

    tree[step].add = 0;                        //父节点清0

}

int a[N];



void build(int l, int r, int step)    

{

    tree[step].left = l;

    tree[step].right = r;

    tree[step].add = 0;

    if (l == r)        //到达叶子节点了

    {

        tree[step].num = a[l];

        return;

    }

    int mid = (l + r) >> 1;

    build(l, mid, L(step));

    build(mid+1, r, R(step));

    //其构造是自下而上(先求出其子节点的值),那么其父亲节点很明显就是其左右节点值的和

    tree[step].num = tree[L(step)].num + tree[R(step)].num;

}



void insert(int l, int r, int step, int add)

{

    if (l<= tree[step].left && tree[step].right <= r)

    {

        tree[step].add += add;    

        tree[step].num += add * (tree[step].right- tree[step].left + 1);    //add * len

        return;

    }

    if (tree[step].add != 0)    //

    {

        update(step);

    }

    int mid = (tree[step].left + tree[step].right) >> 1;

    if (mid >= l) 

        insert(l, r, L(step), add);

    if (mid < r)

        insert(l, r, R(step), add);

    //由于可能其子节点有更新,我们需要在重新计算一次step这层的值

    tree[step].num = tree[L(step)].num + tree[R(step)].num;

}



int query(int l, int r, int step)

{

    int ans = 0;

    if (l <= tree[step].left && tree[step].right <= r)

    {

        return tree[step].num;

    }

    if (tree[step].add != 0)

    {

        update(step);

    }

    int mid = (tree[step].left + tree[step].right) >> 1;

    if (mid >= l)    //与左节点有交集

        ans += query(l, r, L(step));

    if (mid < r)    //与右节点有交集

        ans += query(l, r, R(step));

    return ans;

}



int main()

{

    int t, n, i, k, l, r;

    char str[10];

    scanf("%d", &t);

    for (k=1; k<=t; k++)

    {

        printf("Case %d:\n", k);

        scanf("%d", &n);

        for (i=1; i<=n; i++)

            scanf("%d", &a[i]);

        build(1, n, 1);

        while (scanf("%s", str) && str[0] != 'E')

        {

            scanf("%d%d", &l, &r);

            if (str[0] == 'Q')

            {

                printf("%d\n", query(l, r, 1));

            }

            else 

            {

                r = str[0] == 'S' ? -r : r;

                insert(l, l, 1, r);

            }

        }

    }

    return 0;

}

 

 

 

 

 

你可能感兴趣的:(线段树)