接下来的几篇博客及本篇博客,都将以讲解题目的形式来加强我们对线段树的理解。
覆盖问题
问题
描述
桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。现在从桌子的前方射来一束平行光, 把盒子的影子投射到了墙上。问影子的总宽度是多少?
输入
第1行:3个整数
L
L
,,
N
N
。,
R
R
()表示墙所在的区间;
N
N
()表示盒子的个数
接下来
N
N
行,每行2个整数,
BR
B
R
(
−100000≤BL≤BR≤100000
−
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
一下,就可以将覆盖的长度累加到根结点。
这里要注意的是:区间可能在负半轴上。
我们所采取的操作是:定义一个变量,读入 L L 后执行操作,在于每次读入 BL,BR B L , B R 时分别加上 x x ,就可以把区间平移到。
标程
#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
,,
N
N
。,
R
R
()表示墙所在的区间;
N
N
(),表示盒子的个数
接下来
N
N
行,每行2个整数,
BR
B
R
,(
−100000≤BL≤BR≤100000
−
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;
}