可持续化线段树简单笔记

萌新一枚,借鉴了许多大佬而总结下的笔记,可能有瑕疵或者错误,望指正。

1用于储存每次修改后所有状态的线段树

2不需要存储整颗树,每次修改只要新增单独的链即可

3区间修改不再用pushdown操作,而是选择在查询的过程中下放lazy标记

代码实现

一,树的存储

struct node{
	int l,r,sum,lazy;
}tree[maxn<<5];
int tot;
int root[maxn];//记录每棵树的根节点

二,建立一棵以root[0]为根的树

int build(int l,int r,int p)
{
	p=++tot;//添加新的节点 
	if(l==r)
	{
	tree[p].sum=a[l];
	return p;	
	}
	int mid=(l+r)>>1;
	tree[p].l=build(l,mid,tree[p].l);//创建左节点 
	tree[p].r=build(mid+1,r,tree[p].r);
	tree[p].sum=tree[tree[p].l].sum+tree[tree[p].r].sum;//向上合并求当前节点的和 
	return p; 
}

三,单点修改

void update(int p,int q,int l,int r,int x,int v)//p为前一次修改的树,q为本次修改 
{
	if(l==r)//如果找到位置 
	{
		tree[q].sum=tree[p].sum+v;
		return;
	}
	tree[q].l=tree[p].l;//复制左右节点 
	tree[q].r=tree[p].r;
	int mid=(l+r)>>1;
	if(x<=mid)
	{
		tree[q].l=++tot;//如果要修改的点在左边,向左边分裂成一条新的链 
		update(tree[p].l,tree[q].l,l,mid,x,v);
	}
	else
	{
		tree[q].r=++tot;//右边同理 
		update(tree[p].r,tree[q].r,mid+1,r,x,v);
	}
	tree[q].sum=tree[tree[q].l].sum+tree[tree[q].r].sum;//合并求和 
}

四,区间修改

void update(int p,int q,int l,int r,int ul,int ur,int v)
{
	tree[q].l=tree[p].l;
	tree[q].r=tree[p].r;
	tree[q].lazy=tree[p].lazy;//复制原有节点的信息 
	if(ul<=l&&r<=ur)//如果当前区间在要修改的区间内,更改lazy标记 
	tree[q].lazy+=v;
	else
	{
		int mid=(l+r)>>1;
		if(l<=ur&&ul<=mid)//如果要修改的区间和左儿子有交集,那么就要新建一个左儿子 
		{
			tree[q].l=++tot;
			update(tree[p].l,tree[q].l,l,mid,ul,ur,v);
		}
		if(r>=ul&&ur>mid)//如果要修改的区间和右儿子有交集,那么就要新建一个右儿子 
		{
			tree[q].r=++tot;
			update(tree[p].r,tree[q].r,mid+1,r,ul,ur,v);
		}
	}
	tree[q].sum=tree[p].sum+(min(ur,r)-max(l,ul)+1)*v;//向上求和,当前节点的值为原先的值加上区间修改的总的值
	//如果ur,ul在区间[l.r]内,那么sum加上(ur-ul+1)*v;
	//如果ur,ul和区间[l,r]有交集,sum加上相交部分乘以v 
}

五,区间查询

int query(int l,int r,int ql,int qr,int lazy,int p)
{
	if(ql<=l&&r<=qr)//查询区间在当前区间内,返回当前节点的值加上lazy所贡献的值 
	return tree[p].sum+lazy*(r-l+1);
	int ans=0;
	int mid=(l+r)>>1;
	if(ql<=mid)//和左节点有交集 
	ans+=query(l,mid,ql,qr,lazy+tree[p].lazy,tree[p].l);//下放lazy标记 
	if(qr>mid)//和右节点有交集 
	ans+=query(mid+1,r,ql,qr,lazy+tree[p].lazy,tree[p].r);
	return ans;
}

六,主函数应用

int main()
{
	int n,q;
	cin>>n>>q;//n个数据,q次操作 
	for(int i=1;i<=n;i++)
	cin>>a[i];
	root[0]=build(1,n,1);//新建一个以root[0]为根节点的线段树 
	int s=0;//作为第s次修改后根节点的编号 
	while(q--)
	{
		int op;
		cin>>op;//输入操作 
		if(op==1)//查询操作 
		{
			int l,r,k;//查询第k次修改后[l,r]的和 
			cin>>l>>r>>k;
			cout<<query(1,n,l,r,0,root[k])<<'\n';
		}
		else if(op==2)//单点修改 
		{
			int x,v;
			cin>>x>>v;
			root[++s]=++tot;
			update(root[s-1],root[s],1,n,x,v);
		}
		else if(op==3)//区间修改 
		{
			int l,r,v;
			cin>>l>>r>>v;
			root[++s]=++tot;
			update(root[s-1],root[s],1,n,l,r,v);
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值