核心知识点:
(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;
}
线段树心得