线段树(一)之点修改

匿名 (未验证) 提交于 2019-12-03 00:18:01

首先,明确一点,线段树并不是什么神奇的东西,只是巧妙地利用了分治的思想,它用于区间的动态查询问题~

线段树由很多下面这样的单元组合起来的(我懒地画图了),其中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); 				} 			} 		} 	} }

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!