线段树这个东西真的够奇怪的。。
线段数的节点表示的是一段区间的值,它要求区间之间必须满足能够相加的条件,不然不能用线段树表示。
线段树包括4中操作,包括创建,区间查询,单点更新和区间更新,而区间更新是一个最难的部分,需要用到标记。。
这里就说一下前三种相对简单的操作。
在这之前,补充两个知识点:
x>> 1 表示的是x/2;
x<<1 表示的是x*2;
|运算: 与偶数进行运算时必在基础上+1,与奇数运算结果不变
创建:
创建的时候是从一开始的区间开始划分,通过二分的思想,将一个大区间分成两个小区间。当左区间等于右区间时,就可已将他看成单个点进行处理了。然后进行回溯,从下往上求出各个节点的值。
代码如下:
//建树
void build (int l,int r,int re) //[l,r]表示节点表示的区间,re表示节点标号
{
if(l==r)
{
tree[re]=a[l];
return ;
}
int mid=(l+r)>>1;
build (l,mid,re<<1); //将区间二分
build (mid+1,r,re<<1|1);
//回溯求出根节点的值
Pushup (re);
}
上面有个Pushup,这个函数是给根节点赋值的
它的代码如下:
//更新根节点的值
void Pushup (int re) //求出根节点的值
{
tree[re]=tree[re<<1]+tree[re<<1|1];
}
下一个就是单点更新了。
单点更新相当于就是对单个左右区间相等的节点的值进行修改 ,这实际上就是从上往下找要修改的单点,修改完之后进行回溯,
依次往上更新根节点的值。
代码如下:
//单点更新
void add (int loc,int data,int l,int r,int re) //loc表示更新的位置,data表示加上的值
{
//表示找到这个单点了
if(l==r)
{
tree[re]+=data;
}
int mid=(l+r)>>1;
//如果小于中间值,那么肯定在左子树那里,不小于的话就去右子树找了
if(loc<=mid)
add (loc,data,l,mid,re<<1);
else
add (loc,data,mid+1,r,re<<1|1);
//别忘记也要更新根节点的值哦
Pushup(re);
}
区间查询:
这一部分就有点难了。
区间查询的意思就是给定一个区间查询这个区间的信息。
让我们想想, 如果节点所表示的区间完全包含在所要查询的区间的话,那么我们可以直接返回它的值就可以了。
如果超出了查询范围了的话, 我们就要进行取舍了。
( 1)如果节点的中点mid大于所要查询的左区间left的话, 那么必定该区间中点左边必定有一部分是在查询区间, 这样我们就可以查询节点的左子树了。千万不能也查询右子树,因为mid与right的相对大小不一定, 如果mid>right那么就返回错误值了。 。
( 2)如果mid<right的话, 与上面同理,查询右子树。
代码如下:
//区间查询
int que (int left,int right,int l,int r,int re) //[left,right]表示要查询的区间
{
if(l>=left&&r<=right) //说明此时节点所代表的区间都在查询区间之内,可以直接返回了
return tree[re];
int mid=(l+r)>>1;
//通过ans记录区间之和.
int ans=0;
//走到这里,说明区间超出查询范围了,需要进行筛选
//如果区间终点大于等于left,说明该区间左边的需要纳入查询范围
if(mid>=left)
{
ans+=que (left,right,l,mid,re<<1);
}
//如果区间终点小于right,说明该区间的右边需要纳入查询范围
if(mid<right)
{
ans+=que (left,right,mid+1,r,re<<1|1);
}
return ans;
}