线段树小结
前言
线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。————百度百科
具体的模样:
(图片来源于互联网)
正题
- 线段树是基于分治思想,用于维护具有区间可加性的信息的二叉树,使用范围比树状数组更广,但时间消耗也更大。
- 线段树的每一个节点代表着一个区间
- 根节点代表着整个区间的统计信息 (1~N)
- 叶子结点代表着[x,x]的信息 (单个元素)
- 对于每个节点,他的左儿子是 [ l ,mid ] , 右儿子 是[ mid+1 , r ] ( mid = (l+r)/2 )
- 线段树的编号
- 根节点的编号为 1, 设 某个节点的 编号 为 P 则,P的左儿子 (ls) 为 P<<1 , P的右儿子 (rs) 为 P<<1|1 ,
- 保存线段树的数组大小开4倍 ,可以保证不会越界。
线段树的基本操作:
- 存储结构:
struct sgt
{//区间左,右 ,data,延迟标记
int l,r,dat,lazy;
#define ls p<<1
#define rs p<<1|1
}t[maxn<<2];
- 建树
void build(int p ,int l ,int r)
{//建树
t[p].l = l ,t[p].r = r;
t[p].lazy = 0;
if(l==r)
{//放在建树里输入可以节省下存放输入数据数组的空间
cin>>t[p].dat;
return;
}
int mid = (l+r)>>1;
//左右递归建树
build(ls,l,mid);
build(rs,mid+1,r);
//回溯,更新P节点信息
updata(p);
}
调用 : build( 1, 1 , n)
- 区间修改+延迟标记
- 需注意理解延迟标记的含义 :该节点信息已修改,但其子节点未更新
void change(int p,int L,int R,int d)
{//区间修改 P为编号 ,L,R 为修改的左右区间 d为增量
// 当之前的延迟标记会对现在的懒标记有影响时,需提前下传
if(L<=t[p].l && t[p].r<=R)
{//[L,R] 完全覆盖P节点时,
t[p].sum += 1ll*d*(t[p].r-t[p].l+1); //修改信息
t[p].lazy += d; //更新延迟标记,
return ;
}
spread(p); //向下传 延迟标记
int mid = (t[p].r+t[p].l)/2;
if(l<=mid) change(ls,L,R,d); //修改的区间与ls有覆盖时
if(mid<r) change(rs,L,R,d); //修改的区间与rs有覆盖时
updata(p);
}
调用 : change( 1, l , r,d)
- 加强mid的理解:
- 延迟标记
void spread(int p)
{//向下传递延迟标记
if(t[p].lazy) //如果当前节点的延迟标记未下传
{//下传,更新
t[ls].sum += t[p].lazy *(t[ls].r-t[ls].l+1);
t[rs].sum += t[p].lazy *(t[rs].r-t[rs].l+1);
t[ls].lazy += t[p].lazy;
t[rs].lazy += t[p].lazy;
t[p].lazy = 0;//延迟标记置空 乘法置1 ( 具体问题具体分析)
}
}
当有多个操作时需要考虑开多个延迟标记,并且考虑延迟标记会不会相互影响,如果有需要对其进行处理。
如 洛谷P3373 同时 有区间加,区间乘 那么就需要维护两个延迟标记的优先级
- 查询 :
ll query(int p,int L,int R)
{//查询
// 当之前的延迟标记会对现在的懒标记有影响时,需提前下传
if(L<=t[p].l && t[p].r<=R) //完全包含
{
return t[p].sum;
}
spread(p);
ll s = 0;
ll mid = (t[p].r+t[p].l)/2;
if(L<=mid ) s += query(ls,L,R); //查询的区间与ls有覆盖时
if(R>mid ) s+=query(rs,L,R); //查询的区间与rs有覆盖时
return s;
}
can you answer thses question I (最大子段和)
Segtree query(int p,int l,int r){//[l,r]询问的区间
if(l<=t[p].l && t[p].r<=r){
return t[p];
}
int mid=(t[p].l+t[p].r)>>1;
if(mid<l)return query(p<<1|1,l,r); //查询的区间完全在mid的左边
else if(r<=mid)return query(p<<1,l,r); //查询的区间完全在mid的右边
else{ //查询的区间在mid的两边
Segtree a,b;
a = query(p<<1,l,r);
b = query(p<<1|1,l,r);
Segtree temp;
temp.dat = max(max(a.dat,b.dat) , a.rmax+b.lmax);
temp.lmax = max(a.lmax , a.sum+b.lmax);
temp.rmax = max(b.rmax , a.rmax+b.sum);
return temp;
}
}
-
错误总结
建树,如果建树出错比如递归那里,就有可能出现读入一般就退出,或者一直读入(少return),死循环
查询,分清题目要维护的是哪种情况
修改,修改时要注意维护好信息,比如提前下传延迟标记等
-
printf大法好,常出bug头发少。