数据结构学习~加法线段树

前言

本文所有内容出自于个人的理解,可能有谬误,还请多多包涵,敬请指正。qq :2039316792
本文中参考的所有文章,将会在专页中一一列出。

正文

用途

  1. 区间修改
  2. 单点修改
  3. 区间查询
  4. 单点查询

时间复杂度

O( nlogn )

概论

使用方法

懒标记

懒标记的懒在于用它的时候它才发挥作用,不用的时候就闲着。OI得讲,当我们需要更新子节点时,我们才将标记下放,否则,我们将其存储起来。

找儿子

inline ll ls(ll x){return x<<1;}
inline ll rs(ll x){return x<<1|1;}

建树

递归建树,若当前节点是叶节点,则用输入的值更新答案数组

void built(ll p,ll l,ll r)
{
	tag[p]=0;
	if(l==r){ans[p]=a[l];return ;}
	ll mid=(l+r)>>1;
	built(ls(p),l,mid);
	built(rs(p),mid+1,r);
	push_up(p);
}

上传

我们将左儿子的值与右儿子的值相加,得到父亲的值,即是上传。

inline void push_up(ll p){ans[p]=ans[ls(p)]+ans[rs(p)];}

标记下放

当我们询问某一个区间的值得时候,我们储存的值显然与目标值不同,所以,我们要将父亲节点的懒标记下放,在寻找目标区间的时候,顺便将目标区间的值修改为目标值。

inline void f(ll p,ll l,ll r,ll k)
{
	tag[p]+=k;
	ans[p]=ans[p]+(r-l+1)*k;
}
inline void push_down(ll p,ll l,ll r)
{
	ll mid=(l+r)>>1;
	f(ls(p),l,mid,tag[p]);
	f(rs(p),mid+1,r,tag[p]);
	tag[p]=0;
}

区间修改

若当前的区间已经恰好完全覆盖了目标区间,则返回答案。否则,我们将目标区间分为当前区间和其他区间,递归求其他区间的答案。

inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
	if(nl<=l&&r<=nr)
	{
		ans[p]+=k*(r-l+1);
		tag[p]+=k;
		return ;
	}
	push_down(p,l,r);
	ll mid=(l+r)>>1;	
	if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
	if(nr>mid)update(nl,nr,mid+1,r,rs(p),k);
	push_up(p);
}

代码

以下代码以洛谷P3372 【模板】线段树 1为例

#include<cstdio>
#define maxn 100100
#define ll long long 
using namespace std;
unsigned ll a[maxn],tag[maxn<<2],ans[maxn<<2],n,m;
inline ll ls(ll x){return x<<1;}
inline ll rs(ll x){return x<<1|1;}
inline void push_up(ll p){ans[p]=ans[ls(p)]+ans[rs(p)];}
void input()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++)scanf("%lld",&a[i]);
}
void built(ll p,ll l,ll r)
{
	tag[p]=0;
	if(l==r){ans[p]=a[l];return ;}
	ll mid=(l+r)>>1;
	built(ls(p),l,mid);
	built(rs(p),mid+1,r);
	push_up(p);
}
inline void f(ll p,ll l,ll r,ll k)
{
	tag[p]+=k;
	ans[p]=ans[p]+(r-l+1)*k;
}
inline void push_down(ll p,ll l,ll r)
{
	ll mid=(l+r)>>1;
	f(ls(p),l,mid,tag[p]);
	f(rs(p),mid+1,r,tag[p]);
	tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
	if(nl<=l&&r<=nr)
	{
		ans[p]+=k*(r-l+1);
		tag[p]+=k;
		return ;
	}
	push_down(p,l,r);
	ll mid=(l+r)>>1;	
	if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
	if(nr>mid)update(nl,nr,mid+1,r,rs(p),k);
	push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
	ll res=0;
	if(q_x<=l&&r<=q_y)return ans[p];
	ll mid=(l+r)>>1;
	push_down(p,l,r);
	if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
	if(q_y>mid)res+=query(q_x,q_y,mid+1,r,rs(p));
	return res;
}
int main()
{
	ll a1,b,c,d,e,f;
	input();built(1,1,n);
	while(m--)
	{
		scanf("%lld",&a1);
		switch(a1)
		{
			case 1 : scanf("%lld%lld%lld",&b,&c,&d);update(b,c,1,n,1,d);break;
			
			case 2 : scanf("%lld%lld",&e,&f);printf("%lld\n",query(e,f,1,n,1));break;			
		}
	}
	return 0;
}

参考文献

后记

线段树真…令人爱恨交加…成也线段树,败也线段树…能用树状数组的时候千万不要用线段树啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!!
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值