浅谈线段树及其例题讲解

简介:

线段树是一种数据结构,它是一种便于区间修改与区间查找的数据结构,而他叫做线段树而不是直线树,这说明他与线段有关,线段树存储的是一个线段(即为左端点与右端点),而他的子节点是左端点到mid与mid+1到右端点的线段(左端点与右端点相等为止)。具体结构如下图:

现在我们对他有了一个概念性的了解,而我们现在想一想,该如何存储他呢?

线段树的存储:

线段树的存储有两种方法

一.指针法

struct node {
   int l, r;	//区间左右端点
   int tag;	//区间的标记信息,可以有多种含义的标记
   node *lch, *rch;	//左右子区间指针
};
node *root;		//线段树的根指针

二.完全二叉树法

用一个结构体数组来存储线段树,结点之间满足完全二叉树关系,即:

tree[i]的父结点是tree[i/2],

左右儿子分别是tree[i*2]tree[i*2+1]。

可是这种方式虽然简单,但是空间存储要求量较大(小心MLE)。

线段树的建构:

void build(int i, int l, int r)//i为当前tree的编号,l为需储存的区间的左节点,r为右端点。
{
    int mid;
    tree[i].l = l;  tree[i].r = r;
    tree[i].cover = 0;    //cover表示是否遍历
    if (r == l ) return;         //是单位区间,则返回
    mid = (l + r) / 2;
    build(i * 2, l, mid);    //寻找接下来的子节点
    build(i * 2 + 1, mid + 1, r);
}

线段树的插入:

只有会了插入,你才可以用线段树来解题。

首先是第一种较简单理解的方法。

void insert(int i, int l, int r)
{
    int mid;
    if (tree[i].cover == 1) return;    //当前区间已经覆盖,返回
    mid = (tree[i].l + tree[i].r) / 2;
    if (l==tree[i].l && r==tree[i].r)  //恰好与当前区间重合
      tree[i].cover = 1;
    else if (r <= mid)                  //仅在左子区间
      insert(i * 2, l, r);
    else if (l >= mid + 1)
      insert(i * 2 + 1, l, r);          //仅在右子区间
    else                                //分在左右区间
    {
        insert(i * 2, l, mid);
        insert(i * 2 + 1, mid + 1, r);
    }
}

相信你可以理解,现在是第二种。

我们可以这样想,我们在前面的插入算法时要考虑当前结点的区间与参数中的插入区间的多种交叉情况,而且参数中传递的插入区间一直在变。更简单的做法是:保持参数中的插入区间一直不变,然后看与当前结点区间的关系,具体实现看代码:

void insert(int i,int l,int r)
{
    if (r<tree[i].l || tree[i].r<l) return;  //不相交,返回
    if (l<=tree[i].l && tree[i].r<=r)        //全盖,返回
    {
        tree[i].cover = 1;
        return;
    }
    insert(i*2,l,r);
    insert(i*2+1,l,r);
}

线段树的统计:

int count(int i)
{
    if (tree[i].cover == 1)
      return tree[i].r - tree[i].l + 1;
    else
      if (tree[i].l == tree[i].r ) return 0;
    else return count(i*2) + count(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个整数M,表示可看到的盒子个数。

样例输入:

1 10 5
2 6
3 6
4 6
1 2
3 6

样例输出:

3

思路分析:

我们可以将盒子看做一条线段,而编号就是他的颜色,所以这一题就变成了在一个大区间内有多少种颜色。

而我们就可以构造一个线段树,先将他们的颜色置为0,当输入盒子时就用insert函数置为相应的编号,最后从线段树的顶部寻找,这样我们可以将出现颜色的bool置为1,最后统计个数,得出答案。

对了,还有一个小处理,左端点可能为负,所以加上100000是个不错的选择(暴力出奇迹)。

代码实现:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int L,R,n,p,ans;
bool col[100005];
struct node{
    int l,r,color;
}tree[600005];
int read()
{
    int x=0,f=1;
    char s=getchar();
    while(s<'0'||s>'9')
    {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>='0'&&s<='9')
    {
        x*=10;
        x+=s-'0';
        s=getchar();
    }
    return x*f;
}
void build(int i,int l,int r)
{
    tree[i].l=l;
    tree[i].r=r;
    tree[i].color=0;
    if(l==r)
        return;
    int mid=(l+r)/2;
    build(i*2,l,mid);
    build(i*2+1,mid+1,r);
}
void Insert(int i,int l,int r,int color)
{
    if(tree[i].l>r||tree[i].r<l)
        return;
    if(l<=tree[i].l&&tree[i].r<=r)
    {
        tree[i].color=color;
        return;
    }
    if(tree[i].color>=0)
    {
        tree[i*2].color=tree[i].color;
        tree[i*2+1].color=tree[i].color;
        tree[i].color=-1;
    }
    Insert(i*2,l,r,color);
    Insert(i*2+1,l,r,color);
}
void Find(int i)
{
    if(tree[i].color==0)
        return;
    if(tree[i].color>0)
    {
        col[tree[i].color]=1;
        return;
    }
    else
    {
        Find(i*2);
        Find(i*2+1);
    }
}
int main()
{
    L=read();
    R=read();
    n=read();
    L+=100000;
    R+=100000;
    build(1,L,R-1);
    int a,b;
    for(int i=1;i<=n;i++)
    {
        a=read();
        b=read();
        a+=100000;
        b+=100000;
        Insert(1,a,b-1,i);
    }
    Find(1);
    for(int i=1;i<=n;i++)
        if(col[i])
            ans++;
    printf("%d",ans);
}

猜你喜欢

转载自blog.csdn.net/weixin_43810158/article/details/86476164