我必须要教会你线段树

线段树小结

前言

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。————百度百科

具体的模样:

img

(图片来源于互联网)

正题

  • 线段树是基于分治思想,用于维护具有区间可加性的信息的二叉树,使用范围比树状数组更广,但时间消耗也更大。
    • 线段树的每一个节点代表着一个区间
    • 根节点代表着整个区间的统计信息 (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的理解:

mid  = ( t[p].r + t[p].l ) /2

  • 延迟标记
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头发少。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值