【高级数据结构】影子的宽度(线段树)

目录(进来瞧一瞧

题目

题目描述

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

输入

第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

线段树

显然这是一道线段树的模板题。

就以此树为例,我们发现线段树存储的,和以往的是有很大区别的,它存储的是一个区间而不是某一点,如树根(1 - 6)它就表示 1 - 6 这个区间

看这幅图可能就更加清晰,每一种颜色所对应的是它所占的区间,以此我们会发现一下两条性质

  1. 每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]
  2. 对于结点k,左孩子结点为2*k,右孩子为2*k+1

(这符合完全二叉树的性质)

而对于一棵线段树,它有一下几点操作

  1. build --- 建树
  2. insert --- 插入
  3. const --- 搜索或叫统计

建树的操作其实比较简单,实质上就是确定每一个点所代表的区间给表示出来,因此我们就需要一个结构体来保存

struct node{
    int l,r;
    int tag;//其他需要进行的操作,单已此题论,表示这块区间是否被覆盖
}tree[4 * M];

你们看到这里的树 tree 开了四倍的大小,这是为什么呢?

其实长度为 n 的线段树有 2n - 1 个节点,不信的同学可以亲自试一下。而在实际操作中我们是要开 4n 大小的(虚节点,有些节点没有子节点,但是我们要把他们算上)

建树

既然是一棵树,就要建树撒,不然怎么用呢?

void build(int step,int L,int R){
    tree[step].l = L;//规定节点 step 所代表的区间
    tree[step].r = R;
    if (L == R)//如果他是叶节点,那么我们就可以直接返回了
        return;
    int mid = (L + R) / 2;//二分儿子节点的区间
    build(step * 2,L,mid);//进一步构建他的左右子节点
    build(step * 2 + 1,mid + 1,R);
}

插入

意思就是插入一条 L - R 的线段,这里还是比较抽象的。

注意看结构体中的 tag 变量,于这道题而言,插入就是改变 tag 的值(这道题中,tag 代表的是这段区间是否被完全覆盖)

不断地往叶子结点搜索,直到搜索到了一个他能完全覆盖的区间

这里有 5 种情况是值得我们讨论的

  1. 当前区间已将被标记为 true,已经被覆盖,那么我们再搜索下去也并没有什么用了,一个区间最多被标记 1 次

  2. 要插入的线段的左右端点刚好等于当前节点所代表的区间,那么我们标记为 true 后,就可直接返回。

  3. 假如要加入的线段只在当前节点的左儿子那儿(r <= mid),那我们继续搜索他的左儿子即可

  4. 右儿子那(mid + 1 <= l),搜索右儿子

  5. 左右儿子各占一部分,我们可以分成两部分来处理,因为在这种情况下,这条线段可以表示为 l - mid 与 mid + 1 - r 这两部分,我们以此将区间分开再搜索左右儿子即可

那有没有可能有一种线段不满足上面的情况呢?答案无疑是否定的,因为一棵线段树的叶子节点是代表的已经是最小线段了(1 - 1 , 2 - 2),一个线段被无限分下来也顶多这样的线段(不可能有小数吧)

void Insert(int step,int L,int R){
    if (tree[step].tag)
        return;
    if (tree[step].l == L && tree[step].r == R){
        tree[step].tag = true;
        return;
    }
    int mid = (tree[step].l + tree[step].r) / 2;
    if (R <= mid)
        Insert(step * 2,L,R);
    else if (L >= mid + 1)
            Insert(step * 2 + 1,L,R);
        else {
            Insert(step * 2,L,mid);
            Insert(step * 2 + 1,mid + 1,R);
        }
}

在这种算法中,我们搜索的线段区间一直变换,且判断了多种情况,那有没有一种更方便的方法呢?

其实我们可以保持搜索的线段不变,看当前节点表示的区间与插入线段的关系

void Insert(int step,int L,int R){
    if (tree[step].r < L || tree[step].l > R)//假如不相交的话,就直接返回
        return;
    if (tree[step].r <= R && tree[step].l >= L){//假如当前插入区间被当前树的区间所覆盖,我们即可赋值后返回
        tree[step].tag = true;
        return;
    }
    Insert(step * 2,L,R);//否则我们就搜索他的儿子,看有没有符合要求的
    Insert(step * 2 + 1,L,R);
}

统计

相当于一次遍历吧,从树根往下搜索,如果有区间被完全覆盖,就返回当前区间的长度,或者他是叶节点,我们就返回(继续搜下去就炸了),都不满足的话,我们就继续搜当前区间的两个儿子,直到这棵树都被遍历一遍后,就可得出答案(固时间复杂度为O(n)

代码

#include<cstdio>
#define M 160000 + 5
#define reg register
 
inline void read(int &x){
    x = 0;
    int f = 1;
    char s = getchar();
    while (s < '0' || s > '9'){
        if (s == '-')
            f = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9'){
        x = x * 10 + s - '0';
        s = getchar();
    }
    x *= f;
}
 
int L,R,N,Fz;
struct node{
    int l,r;
    int tag;
}tree[4 * M];
 
void build(int step,int L,int R){
    tree[step].l = L;
    tree[step].r = R;
    if (L == R)
        return;
    int mid = (L + R) / 2;
    build(step * 2,L,mid);
    build(step * 2 + 1,mid + 1,R);
}
void Insert(int step,int L,int R){
    if (tree[step].tag)
        return;
    if (tree[step].l == L && tree[step].r == R){
        tree[step].tag = true;
        return;
    }
    int mid = (tree[step].l + tree[step].r) / 2;
    if (R <= mid)
        Insert(step * 2,L,R);
    else if (L >= mid + 1)
            Insert(step * 2 + 1,L,R);
        else {
            Insert(step * 2,L,mid);
            Insert(step * 2 + 1,mid + 1,R);
        }
}
/*void Insert(int step,int L,int R){
    if (tree[step].r < L || tree[step].l > R)
        return;
    if (tree[step].r <= R && tree[step].l >= L){
        tree[step].tag = true;
        return;
    }
    Insert(step * 2,L,R);
    Insert(step * 2 + 1,L,R);
}*/
int Const(int step){
    if (tree[step].tag)
        return tree[step].r - tree[step].l + 1;
    else if (tree[step].l == tree[step].r)
            return false;
        else
            return Const(step * 2) +  Const(step * 2 + 1);
}
int main(){
    read(L),read(R),read(N);
    if (L < 0)
        Fz = -1 * L;
    L = 0;
    R += Fz;
    build(1,L,R - 1);
    for (reg int i = 1;i <= N; ++ i){
        int x,y;
        read(x),read(y);
        x += Fz;
        y += Fz;
        Insert(1,x,y - 1);
    }
    printf("%d\n",Const(1));
    return 0;
}
 

猜你喜欢

转载自blog.csdn.net/qq_43904786/article/details/86491623