线段树
线段树是一棵二叉树,它的每一个节点用于维护一个区间,其叶节点表示一个点或者单位区间。根维护整个区间。所有区间是它父亲结点二等分后的一个子区间。当有n个元素时,对每一个元素的操作的复杂度是
。
线段树示例
线段树作为一种数据结构,使用范围非常广泛,我们可以在其中添加许多域,令其能够解决更多的问题。
存储
不难得出,我们使用结构体来做最为方便。
由于线段树每一个结点维护的是一个区间,所以,我们可以用两个变量l,r来记录区间;同理,我们就可以在结构体内部加上各种变量来维护这个区间。
线段树的存储方式与二叉树一样,在这里我们采用全局数组存储(当然你也可以使用你的方法)。
示例代码:
struct node{
int l,r;
}Tree[4*Maxn+5];
//经验表明,我们必须使用这么大的数组
//因为线段树有2*N个节点,且生长极不规律
//按这种方法存储,则每个节点的两个儿子分别是2i,2i+1
//否则RE
//当然用指针做更好(不会浪费过多空间)
建树
存储做好了,我们就开始建树。
我们运用递归的思路来建树。
根据线段树的定义,我们可以递归地将区间
分成
和
两个子区间,再递归地分下去,直到分成了一个一个的单位区间。
示例代码:
void Build(int i,int l,int r) {
//i-当前结点编号,l,r当前区间左右端点
Tree[i].l=l;
Tree[i].r=r;
//记录左右端点
if(l==r) {
//中间N多初始化操作
return;
}//处理单位区间
int mid=(l+r)/2;
Build(i*2,l,mid);
Build(i*2+1,mid+1,r);
//递归地分下去
PushUp(i);//别忘了把上传标记
}
我们就得到了一棵空的线段树,接下来就是遍历。
遍历
我们可以利用线段树是一棵二叉树的特点,使用递归来遍历它。
伪代码如下:
DFS(i) //i-当前结点编号
DFS(i*2);//遍历左子树
DFS(i*2+1);//遍历右子数
//接下来的N多操作
//当然你也可以加上一些参数,使其变为一个统计函数
更新
线段树的更新可以很方便用地分治实现。
我们可以很快得出以下代码:
void Update(int i,int l,int r,int val) {//i-当前结点编号,[l,r]要修改的区间,val目标值
int mid=(Tree[i].l+Tree[i].r)/2;
if(l==Tree[i].l&&r==Tree[i].r) {//到达指定区间[l,r]
//修改值
return;
}
PushDown(i);//下传标记
if(r<=mid)
Update(i*2,l,r,val);
else if(l>=mid+1)
Update(2*i+1,l,r,val);
else {
Update(i*2,l,mid,val);
Update(i*2+1,mid+1,r,val);
}
//以上if-else嵌套是分治更新
//如果超出左子区间,则以当前区间的中点二分,递归进行更新
PushUp(i);//上传标记
}
如果我们不去改变区间
,又该如何实现呢?
其实很简单:我们只需不停地遍历整棵树,找出含有区间
的区间,打上标记并返回,等到下一次更新时路过这个节点时将其标记下传
void Update(int i,int l,int r,int val) {
if(r<Tree[i].l||l>Tree[i].r)return;/*未在区间则返回*/
if(/*已被更新则返回*/)return;
if(l<=Tree[i].l&&Tree[i].r<=r) {
//打上标记
return;//返回
}
PushDown(i);
Update(i*2,l,r);//直接找子区间
Update(i*2+1,l,r);
PushUp(i);
}