线段树的简单应用

一:引入:

线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。

线段树可以在O(Log N )的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。

二:建树:

线段树将每个长度不为1的区间划分成左右两个区间递归求解,把整个线段划分为一个树形结构,通过合并左右两区间信息来求得该区间的信息。

例如:我们需要建造一个大小为5的数组A的线段树,A{10,11,12,13,14},此时我们可以引入另外一个数组d来保存这个线段树,d(i)来保存节点为i时的信息。 

这里引用一下oiWIki的图:

我们可以看到,根节点保存的是全部元素的总和,左下节点则为1~(5+1)/2的信息,右下节点则为(5+1)/2+1~5的信息 而左下节点我们称之为左儿子,反之,右下节点则为右儿子,他们分别和其父节点存在一个关系,左儿子=父亲*2,右儿子=父亲*2+1;代码这样表示:(inline可以有效防止无需入栈的信息入栈,节省时间和空间。)

inline int ls(int p)
{
	return p<<1;
}
inline int rs(int p)
{
	return (p<<1)|1;
}

 然后是push_up操作(我这样称呼,虽然不知道正不正规),这个操作主要是对线段树存的什么信息的操作,比如我们求子节点的和,那么代码为:

void push_up_sum(int p)
{
	ans[p]=ans[ls(p)]+ans[rs(p)];
}

然后是递归建树:(ans存的是节点的信息)

​
void build(int l,int r,int p)//左,右,节点
{
	if(l==r)
	{
		ans[p]=a[l];
		return ;
	}
	int mid=l+((r-l)>>1);//这样写可以防止爆int,和r+l>>1效果一样
	build(l,mid,ls(p));
	build(mid+1,r,rs(p));
	push_up_sum(p);
}

​

这样,我们的线段树就整好了。

三:区间维护和查询: 

因为本蒟蒻只会区间修改和查询,所以这个博客就只讲这个了。。。。。

 对于修改,我们需要引入一个懒惰标记

懒惰标记,简单来说,就是通过延迟对节点信息的更改,从而减少可能不必要的操作次数。每次执行修改时,我们通过打标记的方法表明该节点对应的区间在某一次操作中被更改,但不更新该节点的子节点的信息。实质性的修改则在下一次访问带有标记的节点时才进行。

开始为这样,引用一下图:(t为懒标记,d为节点信息)

例如,我们要将区间(3,5)全部元素加上一个数x,那么节点d[3]则要加上(5-3+1)*5,那么其子节点也要进行类似操作,并且子节点的懒惰标记全部变成了x,因为父节点更新了子节点的信息,那么此时父节点的懒惰标记变为0,用代码可以这样表述:

void Change(int p,int l,int r,int l1,int r1,int k)//节点,左,右,修改的左,右,修改值
{
	if(l1<=l&&r<=r1)//当当前区间为要修改区间的子集,那么可以更新该区间的信息
	{
		ans[p]+=(r-l+1)*k;
		b[p]+=k;
		return ;
	}
	int mid=l+((r-l)>>1);
	if(b[p])//更新该区间的子节点信息,并且把懒惰标记清除
	{
		ans[p*2]+=b[p]*(mid-l+1),ans[p*2+1]+=b[p]*(r-mid);
		b[ls(p)]+=b[p],b[rs(p)]+=b[p];
		b[p]=0;
	}
	if(l1<=mid)
	Change(p*2,l,mid,l1,r1,k);
	if(r1>mid)
	Change(p*2+1,mid+1,r,l1,r1,k);
	push_up_sum(p);
}

下面是区间查询:

int get_sum(int p,int l,int r,int l1,int r1)
{
	if(l1<=l&&r<=r1)//当前区间为询问区间的子集时直接返回其数值即可
	{
		return ans[p];
	}
	int mid=l+((r-l)>>1);
	if(b[p])//检查一下,防止漏节点未更新
	{
		ans[p*2]+=b[p]*(mid-l+1),ans[p*2+1]+=b[p]*(r-mid);
		b[ls(p)]+=b[p],b[rs(p)]+=b[p];
		b[p]=0;
	}
	int sum=0;
	if(l1<=mid)sum+=get_sum(2*p,l,mid,l1,r1);
	if(r1>mid)sum+=get_sum(2*p+1,mid+1,r,l1,r1);
	return sum;
}

四:练习一下: 

 

这个题目刚好是我讲的知识点:

下面我抛砖引玉:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+45;
int d[N*4],a[N*4],b[N*4];
int n,m;
void build(int l,int r,int p)
{
	if(l==r)
	{
		d[p]=a[l];
		return ;
	}
	int mid=l+((r-l)>>1);
	build(l,mid,2*p);
	build(mid+1,r,2*p+1);
	d[p]=d[p*2]+d[p*2+1];
}
void change(int l,int r,int p,int l1,int r1,int k)
{
	if(l1<=l&&r<=r1)
	{
		d[p]+=(r-l+1)*k;
		b[p]+=k;
		return ;
	}
	int mid=l+((r-l)>>1);
	if(b[p])
	{
		d[2*p]+=b[p]*(mid-l+1),d[p*2+1]+=b[p]*(r-mid);
		b[2*p]+=b[p],b[2*p+1]+=b[p];
	}
	b[p]=0;
	if(l1<=mid)change(l,mid,2*p,l1,r1,k);
	if(r1>mid)change(mid+1,r,2*p+1,l1,r1,k);
	d[p]=d[p*2]+d[p*2+1];
}
int get_sum(int l,int r,int p,int x,int y)
{
	if(x<=l&&r<=y)return d[p];
	int mid=l+((r-l)>>1);
	if(b[p])
	{
		d[2*p]+=b[p]*(mid-l+1),d[p*2+1]+=b[p]*(r-mid);
		b[2*p]+=b[p],b[2*p+1]+=b[p];
	}
	b[p]=0;
	int sum=0;
	if(x<=mid)sum=get_sum(l,mid,2*p,x,y);
	if(y>mid)sum+=get_sum(mid+1,r,2*p+1,x,y);
	return sum;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,n,1);
	while(m--)
	{
		int x,y,z,k;
		cin>>x>>y>>z;
		if(x==1)
		{
			cin>>k;
			change(1,n,1,y,z,k);
		}
		else
		{
			cout<<get_sum(1,n,1,y,z)<<endl;
		}
	}
	return 0;
}

 五:收工。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值