一听到树,估计很多人会觉得头疼,因为树形结构算是一种比较难的数据结构了,有一系列的公式啊,概念啊什么的,代码实现也是比较麻烦。但其中也有一些很基础,很好实现近乎模板的数据结构。前面的树状数组是一种,这里的线段树也是这样的一种。
树状数组的话可以说相对做法较为单一,要么是单点修改,区间查找,要么是区间修改,单点查找,不这样的话几乎是要爆炸的。除非用什么进阶方法,反正我是不会的。
线段树相对来讲应用还是比较广的,我们可以区间修改,区间查找,但实际上线段树并不是十分快速。
专业术语就不搬出来了,总的来说就相当于是一个坐标轴,有我们利用二分的方法将这样的一个区间表示为树即可。
如图,就是这样一种方式,我们分到不能再分(最小单位)的时候就构造出了这样的一棵树。
这样的线段树用代码是很好表现与实践的,因为我们这样构造的一种二叉树,i的左儿子的下表就是i * 2,右儿子就是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个整数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
题目还是比较简单,相信大家看了有关线段树的介绍也一定在思索如何用线段树做。代码暂时不管这么多,思路还是要想出来。先介绍一种不是用线段树的做法。
我们可以用O(N)的遍历,在遍历到左端点时,计数器加1,到了右端点时,计数器减1,当计数器为0的时候,我们便不统计长度,也就是最终答案ans不加,否则就加1。
也是比较简单的,还有一种方法可以运用离散化来解决。
但很明显,这道题可以用线段树来做,首先,整个墙可以当作x轴,是一个大的线段。每一个正方形就是我们需要插入的小线段。最后我们需要统计长度,我们令线段树中标识区间[l,r]表示l到r + 1这一段线。将线段插入,覆盖一些区间,最后统计即可。
用完全二叉树来储存的话,对于n个单位区间的线段树,我们需要开4n的空间。当然也可以不用完全二叉树,那个就需要用到指针。
我们用结构体中的一个变量cover来表示是否被覆盖。被覆盖了的区间的长度总和便是所求答案。因此不仅我们需要一个插入函数,还需要一个求值函数。并且最开始求解我们需要一个构造线段树的函数。
不过,再这样一种数据结构中进行操作,时间复杂度并不高,每一个函数调用的时间复杂度都是log(n)。
#include
#include
#include
#include
#include
#include
#include
#define N 100005
using namespace std;
struct node {
int l , r , cover;
};
node tree[N * 8];
int n,l,r;
void build(int i ,int x,int y){
tree[i].l = x , tree[i].r = y;
if (x == y)
return ;
int mid = (x + y) / 2;
build(i * 2 , x , mid);
build (i * 2 + 1 , mid + 1,y);
}
void insert (int i , int x , int y ){
if ((y < tree[i].l || x > tree[i].r) || tree[i].cover)
return ;
if (x <= tree[i].l && y >= tree[i].r){
tree[i].cover = 1;
return ;
}
insert(i * 2 , x , y);
insert(i * 2 + 1 , x , y);
}
int add(int k){
if (tree[k].cover)
return tree[k].r - tree[k].l + 1;
if (tree[k].l == tree[k].r)
return 0;
else{
int tot = 0;
tot += add(k * 2);
tot += add(k * 2 + 1);
return tot;
}
}
int main(){
scanf ("%d%d%d",&l,&r,&n);
l += 100000;
r += 100000;
build(1,l,r - 1);
for (int i = 1 ;i <= n;i ++){
int x , y;
scanf ("%d%d",&x,&y);
x += 100000;
y += 100000;
insert(1,x,y - 1);
}
printf("%d",add(1));
}
总的来看难度不大,类似模板,但统计函数我们是可以不要的,我们在插入函数中就可以直接统计出sum值了,如果全部覆盖,那么这一段的sum可以直接弄出来。就是这一个区间的长度,如果没有全部覆盖,当前的这一个节点的覆盖长度便是两个子节点的覆盖长度之和,这样是可以少一个函数,要轻松一些。
没啥说的,反正就是一些题目呢需要应用这一个数据结构来进行优化,单独用的概率不大,因而记住模板的同时我们需要灵活改变。