线段树可以快速对一组数列进行操作,区间求和,区间最值等。
线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。
线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。
看图片,就是一个二叉树。最底层的叶子节点存的是原始数列的数据,每两个数据配对,把两个数据的信息存给父节点。一直递推到根节点。上图节点上代表的该节点存的是这个区间内的信息,比如该区间内的和。
以下代码用到的数组,下标全部从1开始使用
以下代码以节点存储和为例,比如 节点[2, 6]存的就是区间内的总和
1、代码实现的准备工作
#include<stdio.h>
#include<stdlib.h>
#define N 100003
struct node{
int l,r; //当前节点的区间范围
int val;//当前区间内的
};
node segtree[N*2];
int a[N];
2、创建二叉树;
void creat_tree(int root,int l,int r)
{
segtree[root].l=l;
segtree[root].r=r;//把当前区间端点存进节点
if(l==r) //到了叶子节点,即最底层
{
segtree[root].val=a[l];
return;
}
int mid=(l+r)/2;
creat_tree(root*2,l,mid); //左子树的创建
creat_tree(root*2+1,mid+1,r); //右子树
segtree[root].val=segtree[root*2].val+segtree[root*2+1].val;//求和
}
3、查询二叉树。
int Query(int root,int left,int right)//查询区间[left,right]
{
int l=segtree[root].l;
int r=segtree[root].r;
if(l>=left&&r<=right)
return segtree[root].val;
int mid=(l+r)/2;
int sum=0;
if(left<=mid)
sum+=Query(root*2,left,right);//区间在左子树
if(right>mid)
sum+=Query(root*2+1,left,right);//在右子树
return sum;
}
4、节点的更新,比如更新区间[kl,kr]为num
void Update(int root,int kl,int kr,int num)//更新区间[kl,kr]为num
{
int l=segtree[root].l;
int r=segtree[root].r;
if(l==r){ //到达叶子结点
segtree[root].val=num;
a[l]=num;
return;//注意返回
}
int mid=(l+r)/2;
if(mid>=kl)
Update(root*2,kl,kr,num);//递归左子树
if(mid<kr)
Update(root*2+1,kl,kr,num);//建立右子树
segtree[root].val=segtree[root*2].val+segtree[root*2+1].val;//对当前节点进行更新
}
这个题用树状数组会简单些,这里用于讲解线段树作为例题。
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 100003
struct node{
int l,r; //当前节点的区间范围
int val;//当前区间内的
};
node segtree[N*2];
int a[N];
int n;
void creat_tree(int root,int l,int r)
{
segtree[root].l=l;
segtree[root].r=r;//把当前区间端点存进节点
if(l==r) //到了叶子节点,即最底层
{
segtree[root].val=a[l];
return;
}
int mid=(l+r)/2;
creat_tree(root*2,l,mid); //左子树的创建
creat_tree(root*2+1,mid+1,r); //右子树
segtree[root].val=segtree[root*2].val+segtree[root*2+1].val;//求和
}
int Query(int root,int left,int right)//查询区间[left,right]
{
int l=segtree[root].l;
int r=segtree[root].r;
if(l==left&&r==right)
return segtree[root].val;
int mid=(l+r)/2;
if(right<=mid)
return Query(root*2,left,right);//区间全部在左子树
else if(left>mid)
return Query(root*2+1,left,right);//全在右子树
else
return Query(root*2,left,mid)+Query(root*2+1,mid+1,right);//两子树都有
}
void Update(int root,int k,int num)
{
int l=segtree[root].l;
int r=segtree[root].r;
if(l==r){ //到达叶子结点
segtree[root].val+=num;
return;//注意返回
}
if(k<=(l+r)/2)
Update(root*2,k,num);//递归左子树
else
Update(root*2+1,k,num);//建立右子树
segtree[root].val+=num;//对当前节点进行更新
}
int main()
{
int T,x,y;
char s[10];
scanf("%d",&T);
for(int h=1;h<=T;h++)
{
printf("Case %d:\n",h);
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
creat_tree(1,1,n);
while(scanf("%s",s), strcmp(s,"End")!=0)
{
scanf("%d%d",&x,&y);
if(!strcmp(s,"Query"))
printf("%d\n",Query(1,x,y));
if(!strcmp(s,"Add"))
Update(1,x,y);
if(!strcmp(s,"Sub"))
{
y=-y; //只需把y变为相反数,就可以完成相减
Update(1,x,y);
}
}
}
return 0;
}