如果给出了一个数组,a[1]~a[N],要你求出它在[l.r]区间的和,同时还要你修改某一个的值时,如果用暴力求解,有时候数据会爆掉,时间复杂度为O(n*n),也无法通过,这时候线段树就派上用场了。
线段树实际上就是一个二叉树,同时它的每个节点上又存有一定的信息。使用线段树,它每完成一次的时间复杂度为O(log2N)。如果要求的区间为[l,r],那么对于任何一个非子叶的节点,它的左儿子为[l,(l+r)/2],右儿子为[((l+r)/2)+1,r]。
每一个节点,由于它要记录信息,先建一个结构体,具体如下
struct Node
{
int L,R;
int sum;
}Node[Max<<2];
后面,我们需要建立一个线段树,在这个过程中采用递归的思想。代码如下:
void build(int i, int l, int r)
{
Node[i].L = l;
Node[i].R = r;
Node[i].sum=0;
if(l == r)
{
Node[i].sum=a[l];
return;
}
int mid = (l + r)>>1; //相当于(l+r)/2
build(i<<1, l, mid); //相当于i*2
build((i<<1)|1, mid + 1, r); //相当于i*2+1
pushUp(i);
}
代码里面,有一个pushUp()函数,这是一个不断更新节点的值的函数,一个节点的值为它的两个分支的和,故代码:
void pushUp(int i)
{
int lson = i<<1;
int rson = lson + 1;
Node[i].sum = Node[lson].sum+Node[rson].sum;
}
题目往往会要求我们更改某一个位置的值,这时就需要一个Update()函数,这个函数仍然使用递归的思想
void update(int i,int loc,int value)
{
if(Node[i].L == Node[i].R)
{
Node[i].sum =value;
return;
}
int mid = (Node[i].L + Node[i].R)>>1;
if(loc <= mid) update(i<<1, loc, value);
else update((i<<1)|1, loc, value);
pushUp(i);
}
同时,我们要满足线段树的查询功能,即可以知道[l,r]区间的sum和,代码如下:
int query(int i, int l, int r)
{
if(Node[i].L == l && Node[i].R == r)
{
return Node[i].sum;
}
int mid = (Node[i].L + Node[i].R)>>1;
if(r <= mid) return query(i<<1, l, r);
else if(l > mid) return query((i<<1)|1, l, r);
else
{
return query(i<<1, l, mid) + query((i<<1)|1, mid + 1, r);
}
}
线段树的代码就大致如上,几乎所有都可以用此模板来写。不同的线段数只是维护的对象不一样,有的维护sum,有的维护max,min,这要依题目而定。