首页 > 代码库 > 线段树心得

线段树心得

核心知识点:

(i)如何构造一颗线段树:

void build(int rt,int L,int R)

{

   if(L==R)

    scanf("%d",&Max[rt]);

   else {  

       int M=(L+R)/2;

      build(rt*2,L,M);

       build(rt*2+1,M+1,R);

       Max[rt]=max(Max[rt*2]+Max[rt*2+1]);

      //把孩子的节点信息更新到叶子节点

     }

} //相当于一个后序遍历。

首先应当明确,整个过程是由后到前的,也正如学长所给的代码,最后一句画龙点睛的讲到是相当于一个后续遍历。怎么呢?当每个部分不是一个元节点的时候,开始的那个if是没有调用的,直接是执行下面的语句。也就是说,一段长度的线段它内部的最大值是没有确定下来的。到最后当每个孩子节点的值确定后才会通过递归调用的方式返回,慢慢的填满整个树的结构。

 

询问:即通过遍历每个在查询范围的小区间,从中选出最大值,ql,qr代表查询的区间

int query(int r,int L,int R)

{

int ans=-1;

int M=(L+R)/2;

if(ql<=L&&R<=qr)

return Max[r]; //区间要全部在查询的区间上(这个是查询的区间比已经有的区间还要大,直接返回已有的最大值)

if(qr<=M) return query(r*2,L,M); //查询的区间在根节点的左孩子上,进入左孩子查询

else if(ql>M) return query(r*2+1,M+1,R); //查询的区间在根节点的右孩子上,进入右孩子查询

else { ans=max(query(r*2,L,M),query(r*2+1,M+1,R));

}

//若是查询区间在左孩子的区间和右孩子的区间都有,则进入该区间的左右孩子查询,并返还其最大值 

//其实你仔细看代码就会发现,真正返回有效值的操作是第一步,但是讨论是必须要分为四个步骤进行的

更新:即先更新每个叶子节点,区间长度为1的节点。叶子节点更新好后,就把节点信息往上更新,直到根节点。

void update(int r,int L,int R)

{ int M=(L+R)/2;

if(L==R) Q[r]=v; ///直到范围长度为1的时候即找到要更新的叶子节点

else {

if(p<=M) update(r*2,L,M);

else update(r*2+1,M+1,R); ///不断缩小搜索范围,与2分有点像。

Q[r]=max(Q[r*2],Q[r*2+1]); ///叶子节点更新后,开始向上更新。

}

}///更新线段树

 

//在这里,r的值相当于目前处理的值的下标,注意,他们所有的值都是通过数组进行保存的,整颗二叉树,注意,这个函数中有两处给Q[r]赋值的地方,这也就是直接赋值和递归赋值的两个端口

/////////////////////////////////////////////////////////

//下面还是参考上次的模版题——排兵布阵

#include <iostream>
#include <stdio.h>
#include <memory.h>
using namespace std;

int n, a[50005];
char sh[15];

int lowbit(int i) //树状数组最巧妙之处:i&(-i)
{
return i&(-i);
} //满足2^k<=t的最大的2^k,其中k为非负整数

void update(int i, int val) //更新函数
{
while(i <= n)
{
a[i] += val;
i += lowbit(i);
}
}

int sum(int i) //求和函数
{
int sum = 0;
while(i > 0)
{
sum += a[i];
i -= lowbit(i);
}
return sum;
}

int main()
{
int i, val, t, x, y, zz = 1;
scanf("%d", &t);
while(t--)
{
memset(a, 0, sizeof(a));
scanf("%d", &n);
for(i = 1; i <= n; i++)
{
scanf("%d", &val);
update(i, val); //在实际的运用中是没有创建这个操作的,直接是使用更新这个方式实现的
}
printf("Case %d:\n", zz++);
while(scanf("%s", sh))
{
if(sh[0] == ‘E‘) break;
scanf("%d %d", &x, &y);
if(sh[0] == ‘A‘) update(x, y);
else if(sh[0] == ‘S‘) update(x, -y);
else printf("%d\n", sum(y)-sum(x-1)); //两段区间和相减
}
}

return 0;
}