线段树 poj3468

线段树,也叫区间数,在各个节点保持着一段区间的信息(可以是该区间的最大值,最小值,第k小的数等等),主要的操作是区间查询,区间更新和建树。因为线段树结构除了最后一层之外是满二叉树,所以操作的复杂度是lgn级别的。树上每个节点的结构大致如下

struct e
{
    int l,r;
    int value;//待保存的信息
    int flag;//延迟更新操作
 
}

每次更新和查询一个区间(s,t)时,从树的根开始向下查询,若当前树上节点cur满足s<=cur.l&&t>=cur.r,即表示我们要查询的区间比当前节点所保存信息的区间大,那么我们就可以使用这个节点保存的信息进行操作,否则则根据mid和s和t的关系继续向下查询,并将查询结果汇总。

查询和更新的操作非常类似,关键的步骤都是延迟标记。下面说一下延迟标记的引入的作用和用法

延迟标记是为了更新到某个区间时只用更新其向上到根的路径上的点的信息,对于它之下的节点来说,完全不知情。就是说如果查询和更新到上层区间时满足了区间条件,即不需要才查询该区间的子节点时,那么更新这个上层区间后本来其自区间也应该被递归更新,但是为了减少时间复杂度,因此这时我们只更新上层区间点的延迟更新域,这个域表示的是对其所有子节点还没来得及更新的操作的值。通过这种方法,我们可以在更新操作时维持lgn的复杂度,但是在查询和更新时要多考虑这个延迟更新域的因素了

下面是poj 3468 的代码:

#include <iostream>
#include <memory.h>
#include <string>
using namespace std;
//poj 3468 A simple Problem
//使用线段树(区间数),分为build,update(这里是区间更新),query(这里是区间查询)
const int MAX=300010;
struct e
{
	int l,r;
	long long sum;
	long long add;//传说中的延迟标记
};
e tree[MAX+1];
int a[MAX+1];//保存原始的数据
void build(int s,int e,int num)
{
	tree[num].l=s;
	tree[num].r=e;
	if(s==e)//表示只有该区间只有一个元素
	{	
		tree[num].sum=a[s];//这时该区间的sum即为原始数据中的值
		
		return ;
	}
	//若区间包含两个和两个以上的空间,则二分建立
	int mid=(s+e)/2;
	build(s,mid,num*2);
	build(mid+1,e,num*2+1);
	tree[num].sum=tree[num*2].sum+tree[num*2+1].sum;
	return;
}
long long query(int s,int e,int num)
{
	if(s<=tree[num].l&&e>=tree[num].r)//如果查询区间包含该点的区间
	{
		return tree[num].sum;
	}
	//这时这一层已经不能满足了,
	tree[num*2].sum+=tree[num].add*(tree[num*2].r-tree[num*2].l+1);
	tree[num*2+1].sum+=tree[num].add*(tree[num*2+1].r-tree[num*2+1].l+1);
	tree[num*2+1].add+=tree[num].add;
	tree[num*2].add+=tree[num].add;
	int mid=(tree[num].l+tree[num].r)/2;
	tree[num].add=0;
	long long m=0,n=0;
	if(s<=mid)
		m=query(s,e,num*2);
	
	if(e>mid)
		n=query(s,e,num*2+1);
	return m+n;
}
void update(int s,int e,int value,int num)//在区间s-t加value,num是当前查找的点号
{
	//这里采用延迟标记的方法
	if(s<=tree[num].l&&e>=tree[num].r)
	{	
		tree[num].sum+=value*(tree[num].r-tree[num].l+1);
		tree[num].add+=value;
		return;
	}
	else
	{
		//如果这一层已经不能满足了,将延迟标记更新到下一层(即保证下一层的值是最新的)
		tree[num*2].sum+=tree[num].add*(tree[num*2].r-tree[num*2].l+1);
		tree[num*2+1].sum+=tree[num].add*(tree[num*2+1].r-tree[num*2+1].l+1);
		tree[num*2].add+=tree[num].add;//更新下一层节点的延迟标记
		tree[num*2+1].add+=tree[num].add;
		tree[num].add=0;//因为这时num的两个孩子都已经更新过了,所以原来的延迟标记取消了
		int mid=(tree[num].l+tree[num].r)/2;
		if(s<=mid)
			update(s,e,value,num*2);
		if(e>mid)
			update(s,e,value,num*2+1);
		tree[num].sum=tree[num*2].sum+tree[num*2+1].sum;
	}
}
int main()
{
	int N,Q;
	scanf("%d%d",&N,&Q);
	for(int i=1;i<=N;i++)
		scanf("%I64d",&a[i]);
	build(1,N,1);
	char c;
	int m,n,k;
	for(int i=1;i<=Q;i++)
	{
		scanf("\n%c",&c);
		if(c=='Q')//表示查询
		{
			scanf("%d%d",&m,&n);
			long long re=query(m,n,1);
			re=printf("%lld\n",re);
		}
		else 
		{
			scanf("%d%d%d",&m,&n,&k);
			update(m,n,k,1);
		}
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值