【树】一步一步写线段树(二)——实战演练(一)覆盖问题与涂色问题

接下来的几篇博客及本篇博客,都将以讲解题目的形式来加强我们对线段树的理解。

覆盖问题

问题

描述

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

输入

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

输出

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

样例

Sample Input 1

0 7 2
1 2
4 5

Sample Output 1

2

Sample Input 2

-10 10 2
-5 2
-2 2

Sample Output 2

7

Sample Input 3

-10 10 3
-7 0
-4 9
-4 2

Sample Output 3

16

Sample Input 4

-100 100 3
-7 2
5 9
2 5

Sample Output 4

16

Sample Input 5

-50 50 4
-2 4
0 6
9 10
-5 30

Sample Output 5

35

分析

这是一道裸的线段树题目,我们只需要在每个结点中维护一个计数域cnt来统计该区间内有多少单位区间被覆盖,同时在Insert操作回溯时PushUp一下,就可以将覆盖的长度累加到根结点。
这里要注意的是:区间可能在负半轴上。

我们所采取的操作是:定义一个变量x,读入 L L 后执行x=L操作,在于每次读入 BL,BR B L , B R 时分别加上 x x ,就可以把区间平移到[0,RL]

标程

#include<cstdio> 
#include<algorithm> 
using namespace std; 
const int Maxn=1000000; 
struct node { 
    int l,r; 
    int cnt; 
}Tree[Maxn*4+5]; 
int N,L,R; 
void Build(int i,int l,int r) { 
    int mid; 
    Tree[i].l=l,Tree[i].r=r; 
    Tree[i].cnt=0; 
    if(r==l)return; 
    mid=(l+r)/2; 
    Build(i*2,l,mid); 
    Build(i*2+1,mid+1,r); 
} 
void PushUp(int i) { 
    Tree[i].cnt=Tree[2*i].cnt+Tree[2*i+1].cnt; 
} 
void Insert(int i,int l,int r) { 
    if(r<Tree[i].l||l>Tree[i].r)return; 
    if(Tree[i].cnt==Tree[i].r-Tree[i].l+1)return; 
    //这里有个小技巧:若涂满了整个区间则返回
    if(l<=Tree[i].l&&Tree[i].r<=r) { 
        Tree[i].cnt=Tree[i].r-Tree[i].l+1; 
        return; 
    } 
    Insert(i*2,l,r); 
    Insert(i*2+1,l,r); 
    PushUp(i); 
} 
int main() { 
    #ifdef LOACL 
    freopen("in.txt","r",stdin); 
    freopen("out.txt","w",stdout); 
    #endif 
    scanf("%d %d %d",&L,&R,&N); 
    int X=-L; 
    L+=X,R+=X; 
    Build(1,L,R-1); 
    for(int i=1;i<=N;i++) { 
        int l,r; 
        scanf("%d %d",&l,&r); 
        l+=X,r+=X; 
        Insert(1,l,r-1); 
    } 
    printf("%d\n",Tree[1].cnt); 
    return 0; 
} 

涂色问题

问题

桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。问从桌子前方可以看到多少个盒子?假设人站得足够远。
这里写图片描述

输入

第1行:3个整数 L L R N N L R R 100000LR100000)表示墙所在的区间; N N 1N100000),表示盒子的个数
接下来 N N 行,每行2个整数BL BR B R ,( 100000BLBR100000 − 100000 ≤ B L ≤ B R ≤ 100000 )表示一个盒子的左、右端点(左闭右开)。越在前面输入的盒子越排在离墙近的位置,后输入的盒子排在离墙远。

输出

第1行:1个整数 M M <script type="math/tex" id="MathJax-Element-32">M</script>,表示可看到的盒子个数。

样例

输入

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

输出

3

分析

这题也是一个裸的线段树题目,但与上一题不同的是,它所维护的域,是一个标记域。这个域所表示的是这个区间的颜色,若其等于-1,则表示该区间是由多种颜色组成,若为其他正整数则表示该区间是由编号为这个数的颜色涂满了整个区间。由此,我们可以在插入更新操作做完后,再做一遍遍历,将纯色的颜色编号记录下来,统计出来的不同颜色的个数即答案。

标程

#include<cstdio> 
#include<algorithm> 
using namespace std; 
const int Maxn=100000; 
struct node { 
    int l,r; 
    int color; 
}Tree[Maxn*8+5]; 
bool colors[Maxn+5]; 
int N,L,R; 
void Build(int i,int l,int r) { 
    int mid; 
    Tree[i].l=l,Tree[i].r=r; 
    Tree[i].color=0; 
    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 color) { 
    if(r<Tree[i].l||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*2+1].color=Tree[i].color; 
        Tree[i].color=-1; 
    } 
    Insert(i*2,l,r,color); 
    Insert(i*2+1,l,r,color); 
} 
void Count(int i) { 
    if(Tree[i].color>=0) { 
        colors[Tree[i].color]=1; 
        return; 
    } 
    if(Tree[i].color==-1) { 
        Count(2*i); 
        Count(2*i+1); 
    } 
} 
int main() { 
    #ifdef LOACL 
    freopen("in.txt","r",stdin); 
    freopen("out.txt","w",stdout); 
    #endif 
    scanf("%d %d %d",&L,&R,&N); 
    int X=-L; 
    L+=X,R+=X; 
    Build(1,L,R-1); 
    for(int i=1;i<=N;i++) { 
        int l,r; 
        scanf("%d %d",&l,&r); 
        l+=X,r+=X; 
        Insert(1,l,r-1,i); 
    } 
    Count(1); 
    int Ans=0; 
    for(int i=1;i<=N;i++) 
        if(colors[i]==1)Ans++; 
    printf("%d\n",Ans); 
    return 0; 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值