线段树入门以及懒标记

直接给例题,先去看题目:洛谷之线段树模板

先给你们看不带懒标记的线段树代码(含解析)

单点修改(70分):

修改区间:O(nlongn)

查询:O(longn)

代码:

#include<bits/stdc++.h>
#define ll long long
const int maxn=1e5+5;
using namespace std;
ll n,m,x,y,k,a[maxn*4],f[maxn];
//n表示数组长度,m表示m次询问,x和y表示区间[x,y]
//k表示这个区间[x,y]均加上k
//f[i]表示原数组,a[i]表示我们的线段树 
void push_up(ll root)//向上更新 
{
	a[root]=a[root*2]+a[root*2+1];//父节点等于左子节点+右子节点。 
}
void build(ll l,ll r,ll root)
{
	if(l==r)//搜到底了,长度为1的区间肯定是l=r 
	{
		a[root]=f[l];
		return;
	}
	ll mid=(l+r)/2;//二分搜索 
	build(l,mid,root*2);//先建左子节点(左树) 
	build(mid+1,r,root*2+1);//后建右子节点(右数) 
	push_up(root);//向上更新,用于更新父节点的值 
}
void update(ll l,ll r,ll root,ll id)//区间更新(这个区间长度为1),位置为id 
{
	if(l==r)//如果l==r,说明我们已经到大id这个点了,那么就加上k 
	{
		a[root]+=k;return ;
	}
	ll mid=(l+r)/2;//二分 
	if(mid>=id)update(l,mid,root*2,id);//如果id在左边 ,我们就去左边 
	else update(mid+1,r,root*2+1,id);//如果id在右边,我们就去右边 
	push_up(root);//修改子节点的值后,我们要向自己的父亲报告,并且更新父节点的值 
}
ll qurey(ll l,ll r,ll root)//查询区间 
{
	ll ans=0;//用来记录这个区间的和的值 
	if(l>=x&&r<=y)return a[root];//如果[l,r]在我们查询的[x,y]内直接返回这个区间的值 
	if(l==r)return a[root];//如果l==r说明,已经找到低了,而且是一个满足[x,y]区间长度为1的一个值。 
	ll mid=(l+r)/2;//二分 
	if(mid>=x)ans+=qurey(l,mid,root*2);//如果[x,y]区间在左边有满足条件,那么我们就把左边满足条件的值都加起来 
	if(mid<y)ans+=qurey(mid+1,r,root*2+1);//如果[x,y]区间在右边有满足条件,那么我们就把右边满足条件的值都加起来 
	return ans;	//最后我们返回的是[x,y]区间的和 
}
int main()
{
	ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
 	cin>>n>>m;
 	for(int i=1;i<=n;i++)cin>>f[i];//输入原始数组 
 	build(1,n,1);//建树 
 	while(m--)//m次询问 
 	{
 		int q;
 		cin>>q;//根据题意,进行操作 
 		if(q==1)
 		{
 			cin>>x>>y>>k;
 			//因为我们是单点修改,因此复杂度为nlongn; 
			for(ll i=x;i<=y;i++)update(1,n,1,i);
		}
		else
		{
			cin>>x>>y;
			//输出[x,y]的总和 
			cout<<qurey(1,n,1)<<endl;
		}
	 }
	return 0;
}

因为:对于 %100% 的数据:1≤n,m≤1e5

显然单点修改的操作是过不了这题的因为n*m已经超过1e8了。下面介绍的是带有懒标记的代码,其复杂度为O(longn)

如果代码看不懂没关系:先去看这个视频:线段树-懒标记

这个视频不需要看完,只需要懂得懒标记是怎么操作就行了

代码:

#include<bits/stdc++.h>
#define ll long long
#define hh 0x3f3f3f3f
const int maxn=1e5+5;
using namespace std;
ll f[maxn],a[maxn*4],sign[maxn*4],x,y,k,n,m;
//n表示数组长度,m表示m次询问,x和y表示区间[x,y]
//k表示这个区间[x,y]均加上k
//f[i]表示原数组,a[i]表示我们的线段树 
//sign[i]表示存放我们的懒标记的值 
void push_up(ll root)//向上更新 
{
	a[root]=a[root*2]+a[root*2+1];//这个子节点向自己的父节点汇报,并更新父节点的值 
}
void build(ll l, ll r, ll root)// 建树 
{
	sign[root]=0;//让我们的标记函数初始化。 
	if(l==r)//搜到底了,长度为1的区间肯定是l=r 
	{
		a[root]=f[l];return ;
	}
	ll mid=(l+r)/2;//二分搜索 
	build(l,mid,root*2);//先建左子节点(左树) 
	build(mid+1,r,root*2+1);//后建右子节点(右树) 
	push_up(root);//向上更新,用于更新父节点的值 
}
void push_down(ll l,ll r,ll root)//下放标记 
{
	if(sign[root])//如果这个root有标记 
	{
		ll mid=(l+r)/2;//二分 
		sign[root*2]+=sign[root];//把这个标记下放到它的左子节点 
		sign[root*2+1]+=sign[root];//把这个标记下放到它的右子节点 
		a[root*2]+=sign[root]*(mid-l+1);//更新左子节点的值 
		a[root*2+1]+=sign[root]*(r-mid);//更新 右子节点的值 
		sign[root]=0;//取消root层的标记 
	}
}
void update(ll l ,ll r ,ll root)//更新区间 
{
	if(l>=x&&r<=y)//如果[l,r]在[x,y]区间内,那么更新[l,r]的值 
	{
		sign[root]+=k;//在root上加上标记 
		a[root]+=(r-l+1)*k;//更新[l,r]的值 
		return ;
	}
	push_down(l,r,root);//把这个标记下放到它的左右两个子节点 
	ll mid=(l+r)/2;//二分 
	if(mid>=x)update(l,mid,root*2);//如果mid左边有[x,y]的区间那么就去左边找 
	if(mid<y)update(mid+1,r,root*2+1);//如果mid右边有[x,y]的区间那么就去右边找 
	push_up(root);修改区间值后,我们要向自己的父亲报告,并且更新父节点的值 
}
ll qurey(ll l,ll r,ll root)//查询区间 
{
	ll ans=0;//用来记录这个区间的和的值 
	if(l>=x&&r<=y)return a[root];//如果[l,r]在我们查询的[x,y]内直接返回这个区间的值 
	push_down(l,r,root);//把这个root层的标记下放到它的左右子节点 
	ll mid=(l+r)/2;//二分 
	if(mid>=x)ans+=qurey(l,mid,root*2);//如果[x,y]区间在左边有满足条件,那么我们就把左边满足条件的值都加起来 
	if(mid<y)ans+=qurey(mid+1,r,root*2+1);//如果[x,y]区间在右边有满足条件,那么我们就把右边满足条件的值都加起来 
	return ans;//最后我们返回的是[x,y]区间的和 
}
int main()
{
	ios::sync_with_stdio(0);cout.tie(0);cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>f[i];//输入原始数组 
	build(1,n,1);//建树 
	while(m--)//m次询问 
	{
		int q;
		cin>>q;//根据题意,进行操作 
		if(q==1)
		{
			cin>>x>>y>>k;
			//因为我们是带有懒标记的区间,因此复杂度为longn; 
			update(1,n,1);
		}
		else
		{
			cin>>x>>y;
			//输出[x,y]的总和 
			cout<<qurey(1,n,1)<<endl;
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线段是一种用来解决区间查询问题的数据结构。在CSND的线段入门指南中,介绍了线段的基本原理和实现方法,并且提供了进阶内容来扩展应用。 线段的基本原理是将待查询的区间划分为若干个较小的子区间,并将每个子区间的信息预处理保存在节点中。通过在上的查询和更新操作,可以有效地解决区间最值、区间修改、区间合并等问题。 在入门阶段,CSND的指南首先介绍了线段的基本结构和构建方法。通过递归思想和分治策略,可以将一个区间划分为两个子区间,并依次构建子区间的线段,最终构建出整个区间的线段。通过优化构建过程,如使用线性时间复杂度的构建方法,可以提高线段的构建效率。 在进阶阶段,CSND的指南介绍了线段的应用扩展。例如,可以使用线段解决静态区间最值查询问题,即在一个不可修改的区间中快速计算最大或最小值。另外,还可以使用线段解决动态区间修改问题,即可以在区间内进行元素的插入、删除、更新等操作,并支持快速的查询操作。 此外,CSND的指南还介绍了线段的一些常见优化技巧,如标记、矩阵状数组等。这些优化方法可以进一步提高线段的查询和更新效率,适用于一些特殊的应用场景。 总的来说,通过CSND的线段入门进阶指南,我们可以全面了解线段的基本原理和常见应用,并学会使用线段解决各种区间查询问题。这对于算法竞赛、数据结构设计等领域都具有重要的实用价值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值