目录(进来瞧一瞧)
题目
题目描述
桌子上零散地放着若干个盒子,盒子都平行于墙。桌子的后方是一堵墙。如图所示。现在从桌子的前方射来一束平行光, 把盒子的影子投射到了墙上。问影子的总宽度是多少?
输入
第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 这个区间
看这幅图可能就更加清晰,每一种颜色所对应的是它所占的区间,以此我们会发现一下两条性质
- 每个节点的左孩子区间范围为[l,mid],右孩子为[mid+1,r]
- 对于结点k,左孩子结点为2*k,右孩子为2*k+1
(这符合完全二叉树的性质)
而对于一棵线段树,它有一下几点操作
- build --- 建树
- insert --- 插入
- 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 种情况是值得我们讨论的
-
当前区间已将被标记为 true,已经被覆盖,那么我们再搜索下去也并没有什么用了,一个区间最多被标记 1 次
-
要插入的线段的左右端点刚好等于当前节点所代表的区间,那么我们标记为 true 后,就可直接返回。
-
假如要加入的线段只在当前节点的左儿子那儿(r <= mid),那我们继续搜索他的左儿子即可
-
右儿子那(mid + 1 <= l),搜索右儿子
-
左右儿子各占一部分,我们可以分成两部分来处理,因为在这种情况下,这条线段可以表示为 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);
}
统计
相当于一次遍历吧,从树根往下搜索,如果有区间被完全覆盖,就返回当前区间的长度,或者他是叶节点,我们就返回(继续搜下去就炸了),都不满足的话,我们就继续搜当前区间的两个儿子,直到这棵树都被遍历一遍后,就可得出答案(固时间复杂度为)
代码
#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;
}