线段数组的延迟标记

在线段树的“区间查询”指令中,每当遇到被询问区间[l,r]完全覆盖当前节点时,我们知道线段树每个节点其实是代表一个小区间,也就是l<A[p].l<A[p].r<r。
我们已经证明,被询问区间[l,r]在线段树上被分成log N个小区间(节点),从而在O(log N)时间内求出答案。
不过,在“区间修改”指令中,如果某个节点被修改区间[l,r]完全覆盖,那么以该节点为根的整棵树中存储信息都会发生改变,若逐一进行更新,将使得一次区间修改指令的时间复杂度增加到O(N),多次询问时M * O(N)。M,N数量级很大时,M * O(N)的时间复杂度是我们不能接受的。

我们试想一下,多次修改后,到最后P节点代表的区间[Pl,Pr]被修改区间[l,r]完全覆盖,并且逐一更新子树P中所有节点,但在之后的查询指令中根本就没有用到,那么更新p的整棵子树就是徒劳的。

这就给我一个优化提示:
我们能不能碰到完全覆盖区间,不马上修改子树,只是在这个节点上做个变化标记,就立即返回。在后续指令中,需要从p向下递归,我们检查p节点是否有标记。若有,更新p的两个子节点,同时为p的两个子节点增加标记,然后清除p的标记。

向节点p增加一个标记,其实就是标识“该节点曾经被修改过,但其子节点尚未被更新”。
这样一来,每次查询和修改指令都会变为O(log N)。

我们还是用树状数组的一个例子:
A Simple Problem with Integers POJ3468
这个题的题干查看A Tiny Problem with Integers(树状数组区间增加、单点查询)博文
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZE=100010;
struct SegmentTree{
	int l,r;
	long long sum,add;
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define sum(x) tree[x].sum
	#define add(x) tree[x].add
}tree[SIZE*4];
int a[SIZE],n,m;

void build(int p,int l,int r)//No.p, [l,r]
{
	l(p)=l,r(p)=r;
	if(l==r) { sum(p)=a[l]; return; }
	int mid=(l+r)/2;
	build(p*2,l,mid);
	build(p*2+1,mid+1,r);
	sum(p)=sum(p*2)+sum(p*2+1);
}

void spread(int p)
{
	if(add(p))
	{
		sum(p*2)+=add(p)*(r(p*2)-l(p*2)+1);
		sum(p*2+1)+=add(p)*(r(p*2+1)-l(p*2+1)+1);
		add(p*2)+=add(p);
		add(p*2+1)+=add(p);
		add(p)=0;
	}
}

void change(int p,int l,int r,int z)
{
	if(l<=l(p)&&r>=r(p))
	{
		sum(p)+=(long long)z*(r(p)-l(p)+1);
		add(p)+=z;
		return;
	}
	spread(p);
	int mid=(l(p)+r(p))/2;
	if(l<=mid) change(p*2,l,r,z);
    if(r>mid) change(p*2+1,l,r,z);
	sum(p)=sum(p*2)+sum(p*2+1);
}

long long ask(int p,int l,int r)
{
	if(l<=l(p)&&r>=r(p)) return sum(p);
	spread(p);
    int mid=(l(p)+r(p))/2;
	long long ans=0;
    if(l<=mid) ans+=ask(p*2,l,r);
    if(r>mid) ans+=ask(p*2+1,l,r);
    return ans;
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	while(m--)
	{
		char op[2]; int x,y,z;
		scanf("%s%d%d",op,&x,&y);
		if(op[0]=='C')
		{
			scanf("%d",&z);
			change(1,x,y,z);
		}
		else printf("%I64d\n",ask(1,x,y));
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值