线段树看了好久,尤其是区间更新的。每个人都有自己的方法,尽管很多在原理上是相同的。个人建议看别人的思想,写自己的代码。不要记什么模板之类的,你保证不会忘?当然你可以把模板保存起来,用的时候拿出来套,但是我不爱这样,总感觉不是自己的东西,不是自己的思想,不爽。言归正传,线段树。学会线段树的前提: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清空。