一:线段树的基本概念
1:概述
线段树,类似区间树,是一个完全二叉树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,基本能保持每个操作的复杂度为O(log n)
性质:假设某节点对应的区间是[a,b],设c=(a+b)/2,则左子树对应的区间是[a,c],右子树对应的区间是[c+1,b],线段树空间复杂度为O(n);
2:维护点及点修改的线段树
(1)构造线段树
函数:build(int begin,int end,int node) //构造一个线段树OwO
每个节点维护区间内最小值
主要思想是递归构造,如果当前节点记录的区间只有一个值,则直接赋值,否则递归构造左右子树,最后把左右较小者赋给当前的节点
int array[MAX_N]; int segtree[4*MAX_N+10]; void build(int node, int begin, int end) { if (begin == end) segTree[node] = array[begin]; /* 只有一个元素,节点记录该单元素 */ else { /* 递归构造左右子树 */ build(2*node, begin, (begin+end)/2); build(2*node+1, (begin+end)/2+1, end); /* 回溯时得到当前node节点的线段信息 */ if (segTree[2 * node] <= segTree[2 * node + 1]) segTree[node] = segTree[2 * node]; else segTree[node] = segTree[2 * node + 1]; } }
(2)查询最小值
函数:Query(int begin,int end,int node,int left,int right) //查询[begin,end]的最小值,后面3个参数是在递归时为了计算方便而使用的.调用的时候用填0,0,n;
//如:我要查询[600,6000]的最小值,就用Query(600,6000,0,0,n);
主要思想是把所要查询的区间[a,b]划分为线段树上的节点,然后将这些节点代表的区间合并起来得到所需信息
int Query(int begin,int end,int node,int left,int right)//查询[begin,end]的最小值 { int leftQ, rightQ; //如果完全不相交,则返回一个充分大的数INF,这里推荐用INT_MAX if (left > end || right < begin) return INF; /* 如果完全包含 */ /* 返回当前节点的值 */ if (begin >= left && end <= right) return segTree[node]; /* 比较左子树和右子树的最小值 */ leftQ = Query(begin,(begin+end)/2,2*node,left,right); rightQ = Query((begin+end)/2+1,end,2*node+1,left,right); /* 返回这个值 */ return min(leftQ,rightQ); }
(3)单节点更新
函数:void Updata(int begin, int end,int node, int ind, int add) //把编号为node的代表[begin,end]区间的节点加上add
void Updata(int begin,int end,int node,int ind,int add)/*单节点更新*/ { //如果当前是叶子节点,则直接更新 if(begin==end) { segTree[node]+=add; return; } int m=(left+right)>>1; //‘>>1‘相当于除以2 if(ind<=m)Updata(left,m,node*2,ind,add); //二分搜索 else Updata(m+1,right,node*2+1,ind,add); //同样的二分搜索 /*更新父节点*/ segTree[node]=min(segTree[node*2],segTree[node*2+1]); }
3.维护区间及区间修改的线段树
(1)概述
给出一个n个元素的数组A1,A2,...,An,你的任务是设计一个数据结构,支持以下两种操作。
Add(L,R,v).把AL,AL+1,...,AR的值全部增加v。
Query(L,R):计算子序列AL,AL+1,...,AR的元素和、最小值和最大值。
(2)构造线段树
函数:void maintain(int o,int L,int R) //构造一个节点O,对应[L,R]区间
int minv[MAX_N],maxv[MAX_N],sumv[MAX_N],addv[MAX_N]; void maintain(int o,int L,int R) { int lc=o*2,rc=o*2+1; //左子树OvO和右子树OwO if(R>L) { sumv[o]=sumv[lc]+sumv[rc];//考虑左右子树 minv[o]=min(minv[lc],minv[rc]); maxv[o]=max(maxv[lc],maxv[rc]); } minv[o]+=addv[o];//考虑add操作 maxv[o]+=addv[o]; sumv[o]+=addv[o]*(R-L+1); }
(3)操作1.add
函数:void update(int o,int L,int R) //add操作
递归访问到的结点全部要调用maintain函数更新
void update(int o,int L,int R) //add操作 { int lc=o*2,rc=o*2+1; if(yl<=L && y2>=R) addv[o]+=v;//递归边界,累加边界的add值 else { int M=L+(R-L)/2; if(yl<=M) update(lc,L,M); if(y2>M) update(rc,M+1,R); } maintain(o,L,R); //递归结束前重新计算本结点的附加信息 }
(4)操作2.Query
函数:void query(int o,int L,int R,int add)
把查询区间递归分解为若干不相交子区间,把各个子区间的查询结果加以合并,但需要注意的是每个边界区间的结果不能直接用,还得考虑祖先结点对它的影响。为了方便,我们在递归查询函数中增加一个参数,表示当前区间的所有祖先结点add值之和
int _min,_max,_sum; //全局变量,目前位置的最小值、最大值和累加和 void query(int o,int L,int R,int add) { if(yl<=L && y2>=R)//递归边界:用边界区间的附加信息更新答案 { _sum+=sumv[o]+add*(R-L+1); _min=min(_min,minv[o]+add); _ max=max(_max,maxv[o]+add); } else //递归统计,累加参数add { int M=L+(R-L)/2; if(yl<=M) query(o*2,L,M,add+addv[o]); if(y2>M) query(o*2+1,M+l,R,add+addv[o]); } }
未完待续。。。