zoukankan      html  css  js  c++  java
  • [数据结构]线段树

    线段树

    一听到树,估计很多人会觉得头疼,因为树形结构算是一种比较难的数据结构了,有一系列的公式啊,概念啊什么的,代码实现也是比较麻烦。但其中也有一些很基础,很好实现近乎模板的数据结构。前面的树状数组是一种,这里的线段树也是这样的一种。

    与树状数组的区别

    树状数组的话可以说相对做法较为单一,要么是单点修改,区间查找,要么是区间修改,单点查找,不这样的话几乎是要爆炸的。除非用什么进阶方法,反正我是不会的。

    线段树相对来讲应用还是比较广的,我们可以区间修改,区间查找,但实际上线段树并不是十分快速。

    概念及构造

    专业术语就不搬出来了,总的来说就相当于是一个坐标轴,有我们利用二分的方法将这样的一个区间表示为树即可。

    如图,就是这样一种方式,我们分到不能再分(最小单位)的时候就构造出了这样的一棵树。

    这样的线段树用代码是很好表现与实践的,因为我们这样构造的一种二叉树,i的左儿子的下表就是i * 2,右儿子就是i * 2 + 1,就不用啥指针这些东西。但这样的话其实耗费的空间是会比较大的,因此就需要慎重而为,没有十足把握不会爆空间就尽量想一下其他的方法。

    直接来一道模板题

    影子的宽度

    题目描述

    桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。现在从桌子的前方射来一束平行光, 把盒子的影子投射到了墙上。问影子的总宽度是多少?

    输入

    第1行:3个整数L,R,N。-100000 <=L<=R<= 100000,表示墙所在的区间;1<=N<=100000,表示盒子的个数
    接下来N行,每行2个整数BL, BR,-100000 <=BL<=BR<= 100000,表示一个盒子的左、右端点(左闭右开)

    输出

    第1行:1个整数W,表示影子的总宽度。

    样例输入

    Sample Input 1
    0 7 2
    1 2
    4 5
    
    Sample Input 2
    -10 10 2
    -5 2
    -2 2
    
    
    Sample Input 3
    -10 10 3
    -7 0
    -4 9
    -4 2
    
    
    Sample Input 4
    -100 100 3
    -7 2
    5 9
    2 5
    
    Sample Input 5
    -50 50 4
    -2 4
    0 6
    9 10
    -5 30

    样例输出

    Sample Output 1
    2
    
    Sample Output 2
    7
    
    Sample Output 3
    16
    
    Sample Output 4
    16
    
    Sample Output 5
    35

    解题思路

    题目还是比较简单,相信大家看了有关线段树的介绍也一定在思索如何用线段树做。代码暂时不管这么多,思路还是要想出来。先介绍一种不是用线段树的做法。

    我们可以用O(N)的遍历,在遍历到左端点时,计数器加1,到了右端点时,计数器减1,当计数器为0的时候,我们便不统计长度,也就是最终答案ans不加,否则就加1。

    也是比较简单的,还有一种方法可以运用离散化来解决。

    但很明显,这道题可以用线段树来做,首先,整个墙可以当作x轴,是一个大的线段。每一个正方形就是我们需要插入的小线段。最后我们需要统计长度,我们令线段树中标识区间[l,r]表示l到r + 1这一段线。将线段插入,覆盖一些区间,最后统计即可。

    用完全二叉树来储存的话,对于n个单位区间的线段树,我们需要开4n的空间。当然也可以不用完全二叉树,那个就需要用到指针。

    我们用结构体中的一个变量cover来表示是否被覆盖。被覆盖了的区间的长度总和便是所求答案。因此不仅我们需要一个插入函数,还需要一个求值函数。并且最开始求解我们需要一个构造线段树的函数。

    不过,再这样一种数据结构中进行操作,时间复杂度并不高,每一个函数调用的时间复杂度都是log(n)。

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<queue>
    #include<algorithm>
    #include<vector>
    #define N 100005
    using namespace std;
    struct node {
        int l , r , cover;
    };
    node tree[N * 8];
    int n,l,r;
    void build(int i ,int x,int y){
        tree[i].l = x , tree[i].r = y;
        if (x == y)
            return ;
        int mid = (x + y) / 2;
        build(i * 2 , x , mid);
        build (i * 2 + 1 , mid + 1,y);
    }
    void insert (int i , int x , int y ){
        if ((y < tree[i].l || x > tree[i].r) || tree[i].cover)
            return ;
        if (x <= tree[i].l && y >= tree[i].r){
            tree[i].cover = 1;
            return ;
        }
        insert(i * 2 , x , y);
        insert(i * 2 + 1 , x , y);
    }
    int add(int k){
        if (tree[k].cover)
            return tree[k].r - tree[k].l + 1;
        if (tree[k].l == tree[k].r)
            return 0;
        else{
            int tot = 0;
            tot += add(k * 2);
            tot += add(k * 2 + 1);
            return tot;
        }   
    }
    int main(){
        scanf ("%d%d%d",&l,&r,&n);
        l += 100000;
        r += 100000;
        build(1,l,r - 1);
        for (int i = 1 ;i <= n;i ++){
            int x , y;
            scanf ("%d%d",&x,&y);
            x += 100000;
            y += 100000;
            insert(1,x,y - 1);
        }
        printf("%d",add(1));
    }

    总的来看难度不大,类似模板,但统计函数我们是可以不要的,我们在插入函数中就可以直接统计出sum值了,如果全部覆盖,那么这一段的sum可以直接弄出来。就是这一个区间的长度,如果没有全部覆盖,当前的这一个节点的覆盖长度便是两个子节点的覆盖长度之和,这样是可以少一个函数,要轻松一些。

    总结

    没啥说的,反正就是一些题目呢需要应用这一个数据结构来进行优化,单独用的概率不大,因而记住模板的同时我们需要灵活改变。

  • 相关阅读:
    luogu P1382 楼房
    luogu P1908 逆序对
    5.28 模拟赛
    POJ 2991 Crane
    残(矩阵快速幂)
    AC日记——拍照 洛谷 P3410
    AC日记——[CQOI2014]危桥 洛谷 P3163
    AC日记——【模板】二分图匹配 洛谷 P3386
    AC日记——[ZJOI2009]假期的宿舍 cogs 1333
    AC日记——城市 洛谷 P1401
  • 原文地址:https://www.cnblogs.com/lover-fucker/p/13566687.html
Copyright © 2011-2022 走看看