hdu4348:To the moon(可持久化线段树)

题目传送门:http://acm.hdu.edu.cn/showproblem.php?pid=4348

题目大意:一开始给你一个序列,并令时间戳为1。现在有4种操作:C l r d:将当前序列的[l,r]全部+d,并令时间戳+1;Q l r:查询当前序列[l,r]的和;H l r t查询t时刻序列[l,r]的和;B t:将时间戳设为t。

分析:考虑到维护历史版本以及区间操作,我们考虑带懒惰标记的可持久化线段树。我们想一下,一次区间操作,最多需要访问多少个节点?本人认为是接近4*log(n)个:


如果我们在每一次操作的时候下放懒惰标记的话,空间就承受不住了。于是我们考虑不要下放标记,但这样就和普通的线段树有些不同,我们在统计答案的时候,要考虑一路上经过的节点的懒惰标记对答案的影响:


如上图,我们再查询[6,8]的和的时候,由于没有下放标记,[5,8]的节点处还有+2标记,它也会影响答案,于是我们在递归每一个节点[l,r]时求出该区间与询问区间[L,R]的交集长度,再乘以懒惰标记,加进答案里。比如上图,我们在递归[5,8]时,发现它与[6,8]的交集长度为3,就将答案+3*2。

这样我们的理论空间复杂度就降到了m*4*log(n),然而还是会炸。于是我们将空间开到稍微比题目空间限制小一点就可以A了(数据也没这么刁钻)。

CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

typedef long long LL;
const int maxn=100100;
const int lg=13;

struct Tnode
{
	LL add,sum;
	Tnode *lson,*rson;
} tree[maxn*lg*2];
Tnode *Root[maxn];
int cur;

int a[maxn];
LL sum[maxn];

int n,m;
int t;

Tnode *New_node()
{
	tree[ ++cur ].add=0;
	tree[cur].sum=0;
	tree[cur].lson=tree[cur].rson=tree;
	return tree+cur;
}

LL Query(Tnode *root,int L,int R,int x,int y)
{
	if ( y<L || R<x ) return 0;
	if ( x<=L && R<=y ) return root->sum;
	
	int mid=(L+R)>>1;
	LL vl=Query(root->lson,L,mid,x,y);
	LL vr=Query(root->rson,mid+1,R,x,y);
	
	int low=max(L,x);
	int high=min(R,y);
	return (long long)(high-low+1)*root->add+vl+vr;
}

bool In(int L,int R,int x,int y)
{
	return !( y<L || R<x );
}

void Update(Tnode *root,int L,int R,int x,int y,LL v)
{
	if ( x<=L && R<=y )
	{
		root->add+=v;
		root->sum+=( (long long)(R-L+1)*v );
		return;
	}
	
	int mid=(L+R)>>1;
	if ( In(L,mid,x,y) )
	{
		Tnode *z=New_node();
		*z=*(root->lson);
		root->lson=z;
		Update(z,L,mid,x,y,v);
	}
	if ( In(mid+1,R,x,y) )
	{
		Tnode *z=New_node();
		*z=*(root->rson);
		root->rson=z;
		Update(z,mid+1,R,x,y,v);
	}
	
	root->sum=root->lson->sum+root->rson->sum+root->add*(long long)(R-L+1);
}

int main()
{
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	
	scanf("%d",&n);
	while ( 1 )
	{
		scanf("%d",&m);
		for (int i=1; i<=n; i++) scanf("%d",&a[i]);
		
		sum[0]=0;
		for (int i=1; i<=n; i++) sum[i]=sum[i-1]+(long long)a[i];
		
		cur=-1,t=0;
		Root[0]=New_node();
		
		for (int i=1; i<=m; i++)
		{
			char c=getchar();
			while ( c!='H' && c!='B' && c!='C' && c!='Q' ) c=getchar();
			if (c=='B') scanf("%d",&t);
			if (c=='H')
			{
				int L,R,lt;
				scanf("%d%d%d",&L,&R,<);
				long long ans=Query(Root[lt],1,n,L,R);
				ans+=sum[R];
				ans-=sum[L-1];
				cout<<ans<<endl;
			}
			if (c=='Q')
			{
				int L,R;
				scanf("%d%d",&L,&R);
				long long ans=Query(Root[t],1,n,L,R);
				ans+=sum[R];
				ans-=sum[L-1];
				cout<<ans<<endl;
			}
			if (c=='C')
			{
				int L,R,x;
				scanf("%d%d%d",&L,&R,&x);
				Root[ ++t ]=New_node();
				*Root[t]=*Root[t-1];
				Update(Root[t],1,n,L,R,x);
			}
		}
		
		n=0;
		scanf("%d",&n);
		if (!n) break;
		printf("\n");
	}
	
	//printf("%d\n",sizeof(tree)/1024);
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值