数据结构——线段树

简介

线段树是一种能够区间查询、区间修改的二叉树。

实现方式

信息学竞赛中,常使用数组下表模拟指针的方式实现。然而在实际情况中,我们并不知道数据量,因此采用动态分配内存,且无法保证堆空间中有正好足够大的连续未分配内存空间,因此节点一个个的申请内存空间。

实现

节点

	struct Node
	{
		Node* lc;
		Node* rc;
		int left;
		int right;
		long long sum;
		int add;
	};

构建线段树

	void built(Node* &ptr,int left,int right,int* array)
	{
		ptr = new Node();
		ptr->left = left;
		ptr->right = right;
		ptr->lc = ptr->rc = NULL;
		ptr->add = 0;
		if(left == right)
		{
			ptr->sum = (long long)array[left];
			return;
		}
		int mid = (left+right)/2;
		built(ptr->lc,left,mid,array);
		built(ptr->rc,mid+1,right,array);
		ptr->sum = ptr->lc->sum+ptr->rc->sum;
	}

标记下传

线段树若区间修改是将所有影响到的节点全部修改,那么将会使O(log n)的时间复杂度替换为O(n),因此我们使用标记表示影响,只修改刚好被覆盖的节点,只有在访问到受到影响却未修改的节点才下传标记,使子节点被修改。

下传标记

	void spread(Node* ptr)
	{
		if(ptr->lc != NULL)
		{
			ptr->lc->sum += (long long)(ptr->add)*(ptr->lc->right-ptr->lc->left+1);  //计算标记对左节点的影响
			ptr->lc->add += ptr->add;  //下传标记到左节点
		}
		if(ptr->rc != NULL)
		{
			ptr->rc->sum += (long long)(ptr->add)*(ptr->rc->right-ptr->rc->left+1);  //计算标记对右节点的影响
			ptr->rc->add += ptr->add;  //下传标记到右节点
		}
		ptr->add = 0;  //清零当前节点的标记
	}

区间查询

	long long query_sum(Node* ptr,int left,int right)
	{
		if(left <= ptr->left && right >= ptr->right) //正好节点范围在查询范围内
		{
			return ptr->sum;
		}
		spread(ptr);
		long long ret = 0;
		int mid = (ptr->left+ptr->right)/2;
		if(left <= mid) ret += query_sum(ptr->lc,left,right);  //左子节点范围与查询范围有重叠
		if(right > mid) ret += query_sum(ptr->rc,left,right);  //右子节点范围与查询范围有重叠
		return ret;
	}

区间修改

	void change(Node* ptr,int left,int right,int d)
	{
		if(left <= ptr->left && right >= ptr->right)  //正好节点范围在修改范围内
		{
			ptr->sum += (long long)d*(ptr->right-ptr->left+1);
			ptr->add += d;
			return;
		}
		spread(ptr);
		int mid = (ptr->left+ptr->right)/2;
		if(left <= mid) change(ptr->lc,left,right,d);  //左子节点范围与修改范围有重叠
		if(right > mid) change(ptr->rc,left,right,d);  //右子节点范围与修改范围有重叠
		ptr->sum = ptr->lc->sum+ptr->rc->sum;
	}

清空申请的内存

清空不用的申请的内存,是个好习惯,避免内存溢出。

	void remove(Node* ptr)
	{
		if(ptr->lc != NULL) remove(ptr->lc);
		if(ptr->rc != NULL) remove(ptr->rc);
		delete ptr;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值