线段树区间更新

线段树看了好久,尤其是区间更新的。每个人都有自己的方法,尽管很多在原理上是相同的。个人建议看别人的思想,写自己的代码。不要记什么模板之类的,你保证不会忘?当然你可以把模板保存起来,用的时候拿出来套,但是我不爱这样,总感觉不是自己的东西,不是自己的思想,不爽。言归正传,线段树。学会线段树的前提:1是熟悉二叉树。我说的熟悉,你得闭着眼睛能想到二叉树的实现原理。2就是理解递归。递归是我的弱项,总是感觉理解不透彻。好了,现在假设这两个条件你都具备了,其实也假设你看过点更新了,因为多数人都是先学点更新。线段树,顾名思

义,就是一棵二叉树,在树的节点存东西。先来个二叉树。

可能现在还不知道画这个干吗?没关系,对着下面的代码片段,马上就理解。

线段树中,二叉树基本是用数组表示的,很少有人用链表

<span style="font-size:18px;">#include<iostream>
#define INI 100000
using namespace std;
struct Arr
{
	int lc,rc;
	int val,add;
}arr[INI*4];
int father[INI*4];</span>

这就是保存二叉树的结构体数组。里面存有左右子树,增量。

下面看一下建树过程、

<span style="font-size:18px;">void build(int i,int l,int r)
{
	int mid=(l+r)/2;
	arr[i].lc=l;
	arr[i].rc=r;
	arr[i].add=0;
	if(l==r)
	{
		arr[i].val=father[l];
		return ;
	}
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	arr[i].val=arr[i<<1].val+arr[i<<1|1].val;
}</span>
就是一个不断的递归的过程,想了半天不知道该怎么解释才好,如果你能拿上面的树手动模拟一下,肯定比我讲什么都有用。

下面是区间更新

<span style="font-size:18px;">void update(int i,int a,int b,int ad)
{
	int mid=(arr[i].lc+arr[i].rc)/2;
	if(a<=arr[i].lc&&b>=arr[i].rc)
	{
		arr[i].add+=ad;
		arr[i].val+=ad*(arr[i].rc-arr[i].lc+1);
	}
	else
	{
		if(a<=mid)
			update(i<<1,a,mid,ad);
		if(b>mid)
			update(i<<1|1,mid+1,b,ad);
		arr[i].val=arr[i<<1].val+arr[i<<1|1].val;
	}
}</span>
看第一个if语句,如果范围符合,就把需要增加的值赋给本节点的add,然后因为本节点的子孙都要加上这个值,所以总共就加了子节点个ad值,也就是(arr[i].rc-arr[i].lc+1)个

其余的跟点更新差不多。

关键看查询这里

<span style="font-size:18px;">int summ;
void quiry(int i,int a,int b)
{
	
	int mid=(arr[i].lc+arr[i].rc)/2;
	if(a<=arr[i].lc&&b>=arr[i].rc)
	{
		summ+=arr[i].val;
	}
	else
	{
		getc(i);
		if(a<=mid)
			quiry(i<<1,a,b);
		if(b>mid)
			quiry(i<<1|1,a,b);
	}
	//return maxx;
}</span>
这个if也很好理解,如果范围符合,就把本节点的val值加到sum中。关键是getc(i)的作用。

<span style="font-size:18px;">void getc(int i)
{
	if(arr[i].add)
	{
		arr[i<<1].add+=arr[i].add;
		arr[i<<1|1].add+=arr[i].add;
		arr[i].add=0;
		arr[i<<1].val+=arr[i<<1].add*(arr[i<<1].rc-arr[i<<1].lc+1);
		arr[i<<1|1].val+=arr[i<<1|1].add*(arr[i<<1|1].rc-arr[i<<1|1].lc+1);
	}
}</span>
仔细看下代码,你可能已经知道它的作用了。就是把访问到的节点的add值下传。如果不理解,可以拿上面的图想一下。比如我们要改变1到2的值。这时你可以看更新段代码。

我们规定上图的节点从上往下,从左往右为1,2,3.....。当递归到第二个节点时,条件满足。只更新了第二个节点的信息。但当我们要访问第4个节点时。如果没有getc(i),它本来属于1到2这个区域,但它的信息没有被更新。所以现在getc(i)的作用已经出来了。当我们要访问第四个节点的信息时。它需要从第一个往下找。找到第二个时,发现它被更新过,这时就把它的更新信息传给子节点。也就是执行getc(i),这时,第四个节点和第五个节点的信息就被更新了。当我们继续找符合条件的节点时,找到的肯定是被跟新过的节点了。同样的,如果第四个节点有子节点,当访问到第四个时,它会把它的更新信息再传到他的子节点。这里你发现了吗。我们的查询总是比更新的脚步慢,这也就保证了我们每次查询到的,都是被更新过的点。哎!口才不好,不知道有没有讲明白。最后有点需要注意,add值下传后,一定要把本节点的add清空。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值