线段树是一种二叉搜索树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,它基本能保持每个操作的时间复杂度为O(lgN)。
假定根结点是长度为2^h的区间,第i层有2^i个结点(层数从0开始),每个结点对应一个长度为2^(h-i)的区间。最大层编号为h,所以结点总数为1+2+4+……+2^h=2^(h+1)-1;所以一般线段树开结构体时需要两倍空间。
当整个区间长度不是2的整数幂时,叶子不全在同一层,但树的最大层编号和结点总数仍满足上述结论。
对于线段树中的每一个下标为i非叶子节点[a,b],它的左儿子下标为2*i表示的区间为[a,(a+b)/2],右儿子下标为2*i+1表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。
把上面的图和话理解了基本就了解线段树了,接下来看看线段树的代码。
线段树有单点更新和区间更新两种情况,我以hihoCoder1077为例讲一下线段树的单点更新。
题目链接
这题就是线段树单点更新的裸题。就是可以动态修改区间中的一个数,动态询问区间最小值。具体看代码注释
#include
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e6+5;
//一般线段树需要开两倍空间,这里1e6大概是2的20次方,所以我开2^21
const int MAX = 1<<21;
//node_id[i]是区间中第i个数在线段树中的节点编号
int n,q,a,b,c,ans,node_id[maxn];
struct segment_tree{
int l,r,w; //l,r区间左右边界,w区间最小值
}tree[MAX];
void build_segment_tree(int i,int l,int r) //建立线段树
{
tree[i].l=l;
tree[i].r=r;
tree[i].w=INF;
if(l==r) //叶子节点
{
node_id[l] = i; //保存叶子节点在线段树中的节点编号
return;
}
//从该节点向下建立线段树
build_segment_tree(i*2,l,(l+r)/2);
build_segment_tree(i*2+1,(l+r)/2+1,r);
}
void update_segment_tree(int i) //线段树单点更新(从下向上更新)
{
if(i==1) return;
int tmp = i/2;
tree[tmp].w = min(tree[tmp*2].w,tree[tmp*2+1].w);
update_segment_tree(tmp);
}
int query_segment_tree(int i,int l,int r) //线段树查询区间最小值
{
if(tree[i].l==l&&tree[i].r==r) return ans = min(tree[i].w,ans);
if(l<=tree[i*2].r)
{
//查询区间全部在该节点的左子节点区间中
if(r<=tree[i*2].r) query_segment_tree(i*2,l,r);
//查询区间包含该节点的左右子节点区间
else
{
query_segment_tree(i*2,l,tree[i*2].r);
query_segment_tree(i*2+1,tree[i*2+1].l,r);
}
}
//查询区间全部在该节点的右子节点中
else query_segment_tree(i*2+1,l,r);
}
int main()
{
scanf("%d",&n);
build_segment_tree(1,1,n);
for(int i=1;i<=n;i++)
{
//叶子节点的最小值置为它本身的值
scanf("%d",&tree[node_id[i]].w);
//从叶子节点向上更新线段树
update_segment_tree(node_id[i]);
}
scanf("%d",&q);
while(q--)
{
scanf("%d%d%d",&a,&b,&c);
if(a==0)
{
ans = INF;
query_segment_tree(1,b,c);//查询区间最小值
printf("%d\n",ans);
}
else
{
tree[node_id[b]].w = c;//动态修改节点最小值
update_segment_tree(node_id[b]);
}
}
return 0;
}