首先,明确一点,线段树并不是什么神奇的东西,只是巧妙地利用了分治的思想,它用于区间的动态查询问题~
线段树由很多下面这样的单元组合起来的(我懒地画图了),其中M=L+(R-L)/2(当然也可以写成(L+R)/2,但我们应该养成用前面那种方式找中值的习惯,当L和R特别大的时候,L+R有可能溢出,不过线段树上一般没什么问题),使用线段树的前提是你求的东西必须满足区间加法
顺便提一下:
如果知道了[l, m] ,[m + 1, r]的信息 就可以知道[l, r]的信息 这个就叫做满足区间加法,也就是上图的【L,M】 + 【M+1,R】 = 【L,R】
如果知道了[1, r] ,[1, l - 1]的信息 就可以知道[l, r]的信息 这个就叫做满足区间减法,也就是上图的【L,R】 - 【L,M】 = 【M+1, R】
举个栗子,你要求最小值,用min(a, b)表示【a, b】区间最小值,那么:min(L,R)=min( min(L,M), min(M+1, R) )——这很好理解的吧~
同样,还有区间最大值,区间和等等~
差不多意思明白了,现在是具体实现问题了,给一个数组a[],数组大小为n,那么如何建树?
首先,一般也使用数组来存储线段树,设为segTree[ ]吧,即:
也就是满足:
方框里的值对应的是是线段树数组的索引,也就是标号,值得注意的是,虽然强调线段树每一个节点与区间相关,但只有根结点的区间的左右端点被记录下来了(因为任何一个节点表示的区间我们都可以从根结点不断二分得到,复杂度只有log n)。
那么问题来了,节点框框里面的 i 什么的是索引,那segTree[i]到底是什么呢?显然这就和你要求什么有关了,比如你要求最大值,那么segTree[i] = max( segTree[2*i+1], segTree[2*i+2]),也就是左右子节点的最大值——这不难理解。如果既要求最大值,又要求最小值,还要求区间和,那怎么办?——很简单啊,线段树节点别用int,而是用一个结构体,一个存最大值,一个存最小值,还有一个存区间和就OK了~
好像有什么不对。。。左右子节点的最大值?它是叶结点怎么办?那必然存的就是a[ ]数组了~
比如:
这样的,叶子结点分别就存储a[0]、a[1]、a[2]、a[3]。当然,这也并不能太死板了,比如你要看去区间里多少个1,那叶子结点肯定就存1或者0了,说存a[]只是大多数情况~还有一个问题,segTree[]要开多大的呢?肯定不会是无穷大的~事实上,我们通常开a[]的4倍大小,这样可以保证不溢出,要证明挺简单的,a[]有n个元素,也就是线段树有n个叶子节点,树高,这是<=(int)log n + 1的,也就是最多有(int)log n+2层,然后等比数列2^(k-1)求前(int)log n+2项和了,大概就是4n了~
建树说完了(具体代码最后一起贴),然后就是查询了,查询没什么好说的,就从根结点往下面找啊,O(log n)复杂度,具体的看下面代码就懂了~
然后,就是开篇第一句话里说的动态了——线段树是可修改的,这可就超级螺旋棒棒糖了~如果不涉及修改,不用线段树也不是不可以,万物皆可预处理——但是如果加上修改的话,修改一次就要重新处理一次,这就很伤了~对于线段树,修改也是很简单的(正如文章标题所说,这里只涉及点的修改,区间的修改下次再说~),首先由根结点向下递归找到要修改的结点,然后从该节点回溯更新祖先节点,也是O(log n)的复杂度——耐斯~~
今天之所以写这篇博客,是因为我们Java课程训练题出现了这个(虽然那一题数据水到暴力可直接过。。。),我就用Java撸了一波线段树代码,就来记录一下啦~本来想再撸一段C++的,后来还是放弃了,因为我觉得,即使不会Java,也能看那几个函数,毕竟和C++基本没有区别~
/*
*【问题描述】老师想知道从某某同学当中,分数最高的是多少,现在请你编程模拟老师的询问。当然,老师有时候需要更新某位同学的成绩.
*【输入形式】输入包括多组测试数据。每组输入第一行是两个正整数N和M(0 < N <= 30000,0 < M < 5000),分别代表学生的数目和操作的数目。 学生ID编号从1编到N。第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩,接下来又M行,每一行有一个字符C(只取‘Q’或‘U’),和两个正整数A,B,当C为'Q'的时候, 表示这是一条询问操作,他询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少,当C为‘U’的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
*【输出形式】对于每一次询问操作,在一行里面输出最高成绩.
*【样例输入】
5 7
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 4 5
U 2 9
Q 1 5
*【样例输出】5 6 5 9
*/
import java.util.*;
public class StudentGrade{
//最大值线段树
static final int INF = -0x3f3f3f3f;
static void build(int rt, int[] segTree, int[] a, int istart, int iend){
if(istart == iend){
segTree[rt] = a[istart];
return;
}
build((rt<<1)+1, segTree, a, istart, (istart+iend)/2);
build((rt<<1)+2, segTree, a, (istart+iend)/2 + 1, iend);
segTree[rt] = Math.max(segTree[(rt<<1)+1], segTree[(rt<<1)+2]);
}
//查询[ql, qr]最大值
static int query(int[] segTree, int rt, int L, int R, int ql, int qr){
int M = L + (R-L)/2, ans = INF;
if(ql <= L && R <= qr) return segTree[rt];
if(ql <= M) ans = Math.max(ans, query(segTree, (rt<<1)+1, L, M, ql, qr));
if(qr > M) ans = Math.max(ans, query(segTree, (rt<<1)+2, M+1, R, ql, qr));
return ans;
}
//更新,a[p] = v
static void update(int rt, int[] segTree, int L, int R, int p, int v){
int M = L + (R-L) / 2;
if(L == R) segTree[rt] = v;
else{
if(p <= M) update((rt<<1)+1, segTree, L, M, p, v);
else update((rt<<1)+2, segTree, M+1, R, p, v);
segTree[rt] = Math.max(segTree[(rt<<1)+1], segTree[(rt<<1)+2]);
}
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int N = sc.nextInt(), M = sc.nextInt();
int[] segTree = new int[N<<2];
int[] a = new int[N];
for(int i = 0; i < N; ++ i) a[i] = sc.nextInt();
build(0, segTree, a, 0, N-1);
for(int i = 0; i < M; ++ i){
String s = sc.next();
if(s.equals("Q")){
int ql = sc.nextInt(), qr = sc.nextInt();
int ans = query(segTree, 0, 0, N-1, ql-1, qr-1);
System.out.println(ans);
}
else{
int p = sc.nextInt(), v = sc.nextInt();
update(0, segTree, 0, N-1, p-1, v);
}
}
}
}
}