[数据结构]线段树

线段树

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

与树状数组的区别

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

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

概念及构造

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

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

这样的线段树用代码是很好表现与实践的,因为我们这样构造的一种二叉树,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可以直接弄出来。就是这一个区间的长度,如果没有全部覆盖,当前的这一个节点的覆盖长度便是两个子节点的覆盖长度之和,这样是可以少一个函数,要轻松一些。

总结

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

猜你喜欢

转载自blog.csdn.net/weixin_43904950/article/details/86572643