一、定义:
线段树,说白了,就是把线段储存在一棵树里。
举一个例子:
数轴上,有一条线段[1,9]:12345678
将其二分:
12345678
1234 5678
12 34 56 78
1 2 3 4 5 6 7 8
这就是一棵线段树。
把数字换成线段,更清楚:
用严谨一点的语言,是一棵二叉树,树中的每一个结点表示了一个区间[a,b]。每一个叶子节点表示了一个单位区间。根节点表示的是所有的区间。
对于每一个非叶结点所表示的区间[a,b],左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b] 。
二、树的表示与建树
我们可以发现,线段树是一颗完全二叉树,(即每个结点都有0或2个子节点)。
完全二叉树的结点有这么一个性质,tree[i]的父节点是tree[i/2],左儿子是tree[i*2],右儿子是tree[i*2+1]。
所以我们可以写出这样的代码:
struct Epic
{
int l,r;//l,r表示区间的左端点和右端点,闭区间。
int C;//C为权值
}tree[4*MAXN];
tree的大小至少要开为4*MAXN。
要建一棵树,也就是初始化一颗树,只需遍历这棵树,把所有结点都附上初值即可。
详见代码:
void built(int i,int l,int r)
{
tree[i].l=l,tree[i].r=r,tree[i].C=0;//附上需要的值
if(l==r) return ;
int mid=(l+r)/2;
built(i*2,l,mid);
built(i*2+1,mid+1,r);
}
当然你也可以用指针储存这棵树。
struct node
{
int l, r;
int C;
node *lch, *rch; //左右子区间指针
};
node *root;
三、插入与删除
在线段树中插入一条[l,r],权值为D的线段。
对于每一个结点所对应的区间,有三种情况。
可能被完全覆盖,可能只被覆盖了一部分,可能压根儿没被覆盖。
那么我们就可以写出这样的程序:
void insert(int i,int l,int r,int D)
{
int mid=(tree[i].l+tree[i].r)/2;//没有覆盖
if(tree[i].r=mid+1) insert(i*2+1,l,r,D); //仅在右子区间
else//分在左右区间
{
insert(i*2,l,mid,D);
insert(i*2+1,mid+1,r,D);
}
}
删除程序与插入程序大同小异,只不过是insert一条权值为0的线段罢了。
但是,假设有一颗以线段[1,15]为根的线段树,其中的所有结点之前都已经插入过,即我们曾经这样附过值:[1,2],[2,3],……,[14,15],[1,3],[3,5],……,[13,15],[1,5],…………,[1,15]。然后删去[1,15]。
那么整个线段树中的所有结点的状态就都与实际不符了,全都需要修改。修改的复杂度就是线段树的结点数。如果线段很长,复杂度就很高了。有可能比直接模拟的复杂度还要高!
怎么办呢?
为了解决这个问题,我们为每个结点增加一个标记,bj。
Step 1:在擦去线段[a,b]之后,给它的左儿子和右儿子都做上标记,令它们的bj=-1。而不需要对整棵树进行修改。
Step 2:以后每次访问某条线段,首先检查它是否被标记,若被标记,则进行如下操作:
① 将该线段的状态改为未被覆盖,并把该线段设为未被标记,bj=0。
② 把该线段的左右儿子都设为被标记,bj=-1。
这样做的原理很简单,以下图为例:
把线段[1,5]擦去后,给[1,3],[3,5]加上标记。
若以后我们需要用到线段[3,4],就必须先访问[3,5],因为[3,5]被标记,我们访问它之后标记就会传递给[3,4]和[4,5]。[3,4]就给标记上了。也就是说,标记会顺着访问[3,4]的路径一直传递下去。
所以当我们需要用到某条线段时,标记就会传到它那里去,使它得到更新,避免错误的发生。而对于那些不用的线段,就没有更新的必要了,因此我们也不会访问到它和更新它,这样就避免了无用功的产生,提高了程序效率。
一句话,如果要删掉一条线段,打上标记,不管它。后来用到它的时候,再删。
传标记的代码:
void clear(int i) //清除结点i的懒标记,并往下传递
{
tree[i].C=0;
tree[i].bj=0;//表示本结点已处理,恢复bj的状态
tree[i*2].bj=-1,tree[i*2+1].bj=-1; //给左右儿子打上懒标记
}
如果tree[i].bj==-1时调用这个函数。
四、数的遍历
数的遍历就是把每个点都走一次。实现很简单,详见代码:
void travel(int i)
{
//do something
if(l==r) return ;
travel(i*2);
travel(i*2+1);
}