前排放各种大佬对于线段树文章:
luogu大佬_皎月半洒花
一、基本概念
什么是线段树?
- 线段树是一种二叉搜索树,与其他树不一样的是:正常的树节点信息是一个,而线段树是一段,所以它每个节点记录左端点、右端点、线段的信息。
优点
当我们需要对【1,n】范围内的任意区间【l,r】进行修改或求区间和,暴力复杂度很高,所以我们需要将每个区间都提前保存下来,需要改哪些就该哪些。
- 使用线段树可以快速的查找某一个节点在线段中出现的次数,查找时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界。
储存
-
每个节点以结构体的方式存储,结构体包含以下几个信息:
(1) 区间左端点、右端点
(2)这个区间要维护的信息
(3)lazy tag(懒标记) -
基本思想:二分
-
基本形式:
(1)每个节点的左孩子区间为[l,mid],右孩子为[mid+1,r]
(2)对于结点k,左孩子为2k,右孩子为2k+1
二、基本操作
1、构建
- 思路
①确定每次二分到的节点的左右端点、左右范围。
②叶子节点,存储要维护的信息。
③状态合并,计算父节点的区间和。
struct node//结构体
{
int l,r,sum,mark;//同上储存
}xds[400001];//记得开4倍!!!线段树xds
/**
用结构体存储若要访问儿子或父亲很方便
直接xds[p<<1] xds[p<<1|1] xdse[p>>1]
*/
void build(int l, int r, int rt)//l,r表示当前节点区间,rt表示当前节点编号
{
xds[rt].l=l;
xds[rt].r=r;
if(l==r){//到达叶子节点
scanf("%d", &xds[rt].sum);
return;
}
int m=(l+r)/2;//二分
build(ls);//建立左子树
build(rs);//建立右子树
xds[rt].sum=xds[rt<<1].sum+xds[rt<<1|1].sum;
//状态合并,此结点的sum=两个孩子的sum之和
}
下传
void down(int rt)//标记下传
{
xds[rt<<1].mark+=xds[rt].mark;
xds[rt<<1|1].mark+=xds[rt].mark;
xds[rt<<1].w+=xds[rt].mark*(xds[rt<<1].r-xds[rt<<1].l+1);
xds[rt<<1|1].w+= xds[rt].f*xds[rt<<2|1].r-xds[rt<<2+1].l+1);
tree[rt].f = 0;
}