星星之火OIer:浅谈线段树

这里带大家简单了解一下C++中的线段树

一、何为线段树

线段树,顾名思义,就是一棵树上的每一个节点都表示的一段线段,例如:

或者

这就是两棵典型的线段树

二、为什么要用线段树

线段树上的每一条线段,都可以增加其他的东西(如权值等),可以动态地维护某些我们需要的信息,这使线段树拥有极大的灵活性,可以适应不同的需求

三、例题

一、

这道题,首先肯定可以用模拟来打代码,比如:

#include<cstdio>
#include<algorithm>
using namespace std;
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-48;
        s=getchar();
    }
    x*=f;
}
inline void pr(int x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9)
        pr(x/10);
    putchar(x%10+48);
}//快读快输不解释
struct node {
    int l,r;
}a[100005];
int i,n,j,k,ans,L,R,s,e;
inline bool cmp(node a,node b) {
    return a.l<b.l;
}
int main() {
    read(L),read(R),read(n);
    for(i=1;i<=n;i++)
        read(a[i].l),read(a[i].r);
    sort(a+1,a+1+n,cmp);//按左端排序
    a[n+1].l=a[n+1].r=2147483647;//a[n+1]一定要定义为无限大,因为每一个区间要和后面的区间比较
                                 //看是否重合,把a[n+1]定义为INT_MAX就可以避免
    for(i=1;i<=n;i++) {
        s=a[i].l;
        e=a[i].r;
        for(;a[i+1].l<=e;i++)//两区间相交
            if(a[i+1].r>e)//取最大的右端点
                e=a[i+1].r;//更新
        ans+=e-s;//计算ans(左闭右开)
    }
    pr(ans);
}

这是一个很简单的模拟算法

但其实也可以用线段树来做

我们把整个墙的长度用区间来表示一下

然后用一棵二叉树来从L~R把每一个区间都二分出来

然后输入每一个区间时都有以下几种操作:

1、全覆盖这个区间,标记一下(cover=1)

2、不覆盖这个区间,return

3、覆盖一部分,左右两边dfs

当所有的线段都输完了之后,从树的根部往下搜,如果当前线段的cover==1,ans+=右端点-左端点(左闭右开)

节约时间,代码就留给大家自己去思考

二、

题目上是从后往前输入

这道题,理论上也是可以用模拟来做的,但这道题我打的是线段树的代码

模拟的话就是在每一次输入的时候把前面的更新一下,但时间复杂度可能比较高

而线段树在这里就比较方便了

我们把题目整理一下,变成了::

在一条线段上上色,每次上色会覆盖前面的颜色,求最后可以看到的颜色的个数

然后就可以做了

我们用一个变量c来存这条线段的颜色,第i个盒子的颜色就是i,底色是0

如果一条线段是混色就标记为-1

然后仿佛又重复上一道题的三个步骤

然后在统计就完事儿辣~\(≧▽≦)/~

代码还是很好懂,萌新在代码里面给大家解释::

#include<cstdio>
inline void read(int &x) {
    x=0;
    int f=1;
    char s=getchar();
    while(s<48||s>57) {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>47&&s<58) {
        x=x*10+s-48;
        s=getchar();
    }
    x=x*f;
}
inline void pr(int x) {
    if(x>9)
        pr(x/10);
    putchar(x%10+48);
}//快读快输就算了吧
struct node {
    int l,r,c;
}a[800005];
int i,n,j,k,m,L,R,ll,rr,o,ans;
bool flag[100005];
void build(int l,int r,int i) {
    int mid=(l+r)/2;
    a[i].l=l,a[i].r=r,a[i].c=0;
    if(l==r)
        return;
    build(l,mid,i*2);
    build(mid+1,r,i*2+1);
}//建树
void insert(int i,int l,int r,int color) {//插入一条线段
    if(r<a[i].l||l>a[i].r) return;//全不覆盖
    if(r>=a[i].r&&l<=a[i].l) {//全覆盖
        a[i].c=color;
        return;
    }
    if(a[i].c>=0) {//覆盖一部分
        a[i*2].c=a[i*2+1].c=a[i].c;//子树继承父亲的颜色
        a[i].c=-1;//当前为混色
    }
    insert(i*2,l,r,color);//没有全部覆盖或全覆盖就继续往两边添加
    insert(i*2+1,l,r,color);
}
void find(int i) {//查找有多少个颜色
    if(a[i].c==0) return;//底色
    if(a[i].c>0) {//单一种颜色
        if(flag[a[i].c]==0) {//没有出现过
            ans++;//多一种
            flag[a[i].c]=1;
        }
        return;
    }
    if(a[i].c==-1) {//混色
        find(i*2);//继续往两边找
        find(i*2+1);
    }
}
int main() {
    read(L),read(R),read(n);
    o=-L;
    L+=o,R+=o;//其实这里没有必要更新为正数,但变为正数后看着舒服些,调试也方便些
    build(L,R,1);//建树
    for(i=1;i<=n;i++) {
        read(ll),read(rr);
        ll+=o,rr+=o;
        insert(1,ll,rr-1,i);//插入
    }
    find(1);
    pr(ans);//找颜色
}

这就是第二题了

三、

我们把第二题改一下,如果线段的颜色可以相同那么最后会有多少种颜色呢?

这里有两种做法:

1、改变find算法,qiyu其余不变

2、增加一个ds表示当前线段被分成的段数,修改insert维护ds值, 降低查询时间

这里着重讲第二种(因为我做的第二种)

我就直接在代码里解释了::

#include<cstdio>
#include<iostream>
using namespace std;
inline void read(int &x) {
    x=0;
    int f=1;
    char s=getchar();
    while(s<48||s>57) {
        if(s=='-')
            f=-1;
        s=getchar();
    }
    while(s>47&&s<58) {
        x=x*10+s-48;
        s=getchar();
    }
    x=x*f;
}
inline void pr(int x) {
    if(x>9)
        pr(x/10);
    putchar(x%10+48);
}//快读快输不解释
struct node {
    int l,r,c,ds/*段数*/,lc/*左端点颜色*/,rc/*右端点颜色*/;
}a[800005];
int i,n,j,k,m,L=2147483647,R=-2147483647,ll[100005],rr[100005],col[100005],o,ans;
bool flag[100005];
void build(int l,int r,int i) {//建树
    int mid=(l+r)/2;
    a[i].l=l,a[i].r=r,a[i].c=a[i].lc=a[i].rc=o;
    if(l==r)
        return;
    build(l,mid,i*2);
    build(mid+1,r,i*2+1);
}
void insert(int i,int l,int r,int color) {
    if(r<a[i].l||l>a[i].r) return;//全不覆盖
    if(r>=a[i].r&&l<=a[i].l) {//全覆盖
        a[i].rc=a[i].lc=a[i].c=color;
        a[i].ds=1;//都要更新
        return;
    }
    if(a[i].c>0&&a[i].c==color) return;//为当前颜色,可以跳过了
    if(a[i].c>0) {//颜色被改变,先传给子树
        a[i*2].lc=a[i*2].rc=a[i*2].c=a[i].c;
        a[i*2+1].lc=a[i*2+1].rc=a[i*2+1].c=a[i].c;
        a[i*2].ds=a[i*2+1].ds=1;
        a[i].c=-1;
    }
    insert(i*2,l,r,color);//左右两边插入
    insert(i*2+1,l,r,color);
    a[i].lc=a[i*2].lc;//往下传递各种信息
    a[i].rc=a[i*2+1].rc;
    a[i].ds=a[i*2].ds+a[i*2+1].ds;
    if(a[i*2].rc==a[i*2+1].lc)a[i].ds--;
}
int main() {
    read(o),read(n);
    for(i=1;i<=n;i++)
        read(ll[i]),read(rr[i]),read(col[i]),L=min(L,ll[i]),R=max(R,rr[i]);
    L--,R++;//保险
    int oo=-L;
    L+=oo,R+=oo;
    build(L,R,1);
    for(i=1;i<=n;i++)
        insert(1,ll[i]+oo,rr[i]-1+oo,col[i]);//每一段都插入
    pr(a[1].ds);//直接输出第一段的颜色就好了
}

好了,今天的博客就到此为止吧,想了解更多,那就给个关注吧

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值