线段树(一)——杨子曰算法

线段树(一)——杨子曰算法

传送门:线段树集合


来来来,先问一个问题,给一个长度为n(n<=200000)的序列,然后是m(m<=200000)个询问,对于每个询问x,y,求区间[x,y]的max
噢!!恶心的数据范围,显然是O(m log n)的节奏啊!
没错,我们今天就要用m log n的复杂度来解决这道题
(ST表也是好东西)


今天我们曰——线段树
首先,它是一棵二叉树,也就是我们用一个数组就可以模拟,即结点i的儿子是i2和i2+1,同理i的父亲是i/2
好,先看下线段树长啥样,Look at the 图,就是当n=5时的线段树
这里写图片描述
对的,每一个结点都表示这一个区间,他的左儿子和右儿子就分别是它的左区间和右区间(注意l和r不需要记录在树上,你可以通过结点编号推出来,首先我们已经知道了一号结点的区间是[1,n],那我们就知道2号结点是[1,n/2],3号是[n/2+1,n],同理,我们知道i号结点的区间是[l,r],那么i*2就是[l,mid],i×2+1[mid+1,r],懂否?)
然后你可以在节点上记录一些关于这个结点的信息,比如这道题,我们就可以记录这段区间的max,具体实现:


1.建树(build)
首先递归到叶子节点,那么这个结点上的值,就是对应的a[i],然后回溯,对于每个tree[nod]](在树中编号为nod的结点),它就等于max(tree[nod×2],tree[nod×2+1]),就是左右区间的最大值的最大值

void build(int l,int r,int nod){
	if (l==r){
		tree[nod]=a[l];
		return;
	}//到叶子节点
	int mid=(l+r)/2;
	build(l,mid,nod*2);//造左子树                   
	build(mid+1,r,nod*2+1)//造右子树
	tree[nod]=max(tree[nod*2],tree[nod*2+1]);//回溯,专业人士称之为pushup
} 

2.解决问题(query)
话说这棵子树造好了依然不能解决问题,询问又不会和树上的区间完全吻合,怎么搞搞

int query(int l,int r,int ll,int rr,int nod){
	//l,r为目前所在结点的左右区间
	//ll,rr为要查询的左右区间
	//nod为目前结点的编号
	int mid=(l+r)/2;
}

如果l=ll && r=rr (就是所在区间与查询区间完全重合)直接返回就好了,可如果不是呢?
在这里,我们要分三种情况讨论
1.要查询的区间都在左区间,即(rr<=mid),那么就往左区间走query(l,mid,ll,rr,nod×2)
2.要查询的区间都在右区间,即(ll>mid),那么就往左区间走query(mid+1,r,ll,rr,nod×2+1)
3.最恶心的是要查询的区间一半在左区间,一半在右区间,那我们把查询的区间一分为二,左边往左走,右边往右走,在取个max,也就是返回max(query(l,mid,ll,mid,nod×2),query(mid+1,r,mid+1,rr,nod×2+1))←仔细琢磨一下

int query(int l,int r,int ll,int rr,int nod){
	if (l==ll && r==rr) return tree[nod];
	int mid=(l+r)/2;
	if (rr<=mid) return query(l,mid,ll,rr,nod*2);
	else if (ll>mid) return query(mid+1,r,ll,rr,nod*2+1);
	else return max(query(l,mid,ll,mid,nod*2),query(mid+1,r,mid+1,rr,nod*2+1));
}

好滴,这道题我们就欧了,再来一道几乎一样的
依然是一个长度为n(n<=200000)的序列,依然是m(m<=200000)个询问对于每个询问x,y,输出区间[x,y]的和
呵呵,不就是变成和了吗,做法一样一样的,线段树上区间记录和就好了呀,吧max改成+不久完事了么
Look at the BUILD(从上面复制来的):

void build(int l,int r,int nod){
	if (l==r){
		tree[nod]=a[l];
		return;
	}//到叶子节点
	int mid=(l+r)/2;
	build(l,mid,nod*2);//造左子树                   
	build(mid+1,r,nod*2+1)//造右子树
	tree[nod]=tree[nod*2],tree[nod*2+1]);//回溯,专业人士称之为pushup
} 

再look at the QUERY(依然是复制来的):

int query(int l,int r,int ll,int rr,int nod){
	if (l==ll && r==rr) return tree[nod];
	int mid=(l+r)/2;
	if (rr<=mid) return query(l,mid,ll,rr,nod*2);
	else if (ll>mid) return query(mid+1,r,ll,rr,nod*2+1);
	else return query(l,mid,ll,mid,nod*2)+query(mid+1,r,mid+1,rr,nod*2+1);

OK,完事
推荐一道题:https://blog.csdn.net/henryyang2018/article/details/79688161
有人说,前缀和搞一搞,O(1)就搞定了,为什么要用这种恶心的办法呢?往下看↓


那我再给道题,也为线段树(二)做个预告:
还是一个长度为n(n<=200000)的序列,还是m(m<=200000)个询问,对于每个询问先给出一个k,若k=1则输出x,y输出区间[x,y]的和,若k=2则输入x,y,将第x个数加上y,88
传送门:线段树(二)
于TJQ高层小区
未经作者允许,严禁转载:https://blog.csdn.net/HenryYang2018/article/details/79836605

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值