上篇文章,我们介绍了线段树的基本概念和单点更新、区间查询,今天,我们来接着上次的线段树问题继续深入研究。在解决线段树问题的过程中,我们会遇到要求修改区间中某一元素值的问题,当然也可能会遇到要求修改一段子区间所有值的问题--即区间更新问题。回忆一下上篇文章单点更新的方法是,由叶节点逐级向上进行更新,此时更新一个节点值的时间复杂度为o(log n),(点击链接了解详情:线段树+RMQ问题第二弹),那么以这样的处理效率来进行区间更新结果会怎样?现在假设待更新区间数据的规模为 n ,那么就需要进行 n 次单点更新,也就意味着处理的时间复杂度为 o(n* log n),这样的时间复杂度其实还是可以接受的,但是,在实际生活中问题怎么可能那么简单就给一个区间呢?这样岂不是让做题的人太舒服了。所以,再假设待查询的区间个数也为 n ,这时候,时间复杂度就变成了 o(n*n*log n),如果这个 n 的值再大一点。。。你敢继续往下想吗?
我大概是不敢了,所以现在需要尝试找一种快捷的方法或者在现有的方法基础上进行优化。
通常情况下,如果时间复杂度过高,我们就会考虑牺牲空间来换取时间,利用线段树来解决区间最值查询问题的思路也是如此,那么在区间更新的问题上,我们应该如何合理利用空间换取时间呢?经过分析可以发现,在区间更新的问题中,我们要做的无非就是取一个区间的信息,接着获取更新的方式,最后定位范围进行更新。如果待更新区间中每个元素的更新方式都不同,那没有办法,我们只能因元素而异,对症下药,但如果待更新区间所有元素都遵循统一的更新规则,例如均加上某个常数,那处理起来就会有一致性,我们就可以以此为切入点进行研究。
我们都知道,元素更新问题一般会伴随着查询问题,如果更新与否对当前面对的所有问题不会造成影响,这时候我们可以借鉴赊账的原理,对需要更新但当前不需要查询的区间暂不更新,并把这笔账记下,待需要查询的时候,将这笔账偿清,这样也并不影响查询过程的正确性。有人喜欢赊账,觉得这年头,花呗上没点旧账都不叫生活,但有的人却不喜欢这种透支未来的生活方式。这里我想说在做题的过程中赊点帐还是无伤大雅的,重要的是,这种赊账的思想用在这里还真是恰到好处呢,不信你就往下看。在继续之前,我大胆的猜测一下,会不会有人因为看了我这篇文章,到实践中检验真理,兜里有钱也去花呗上赊点帐玩玩。
现实中我们要赊账打开支付宝就可以了,那么在解决区间更新的问题中,我们如何赊账呢?方法很简单,我们需要牺牲空间用来记录线段树上的每个节点是否需要更新,如果需要更新,需要更新的规则是什么。以给区间上的每个元素加一个值 x 为例,如果该节点所表示的区间需要更新,那我们就可以将对应的赊账标记记为 x ,当需要查询该区间时,对该区间进行更新,并清除赊账标记,如果该节点所表示的区间不需要更新,对应的赊账标记记为 0 。这样做的好处就在于,省去了更新无需查询区间的值,如果一次查询的过程中,只更新不查询的操作比较多,那可以料想到,节省的时间将是不可估量的。
总是赊账这么叫,看起来好 low ,其实,这个方法有个高大上的名字--Lazy 思想,赊账标记也就叫做——Lazy Tag 。
这个想法浅显易懂,很容易就想明白了,但其发挥的作用却是无法估量的,正所谓大道至简,有时候也许就是这样浅显的道理、细微的改进效果就可以非常显著,还有时候比别人再多往前迈进一小步,就能看见另一番天地。无论是钻研问题还是生活,这个道理都同样适用。
接下来我给大家展示一下核心代码部分,希望大家看了代码能对此有更加深入的体会。
由于上篇文章中,我们介绍的线段树存储的是区间最小值,今天就以之前的假设为基础,深入研究。
以上就是今天的代码分享,发现其中有错误或者有更简洁方法的朋友可以在下方留言区留言,或者点击作者名片添加微信好友私聊,不胜感激。
还没关注公众号的朋友可以长按下方图片识别二维码关注呦。
长按二维码识别关注
代码的网页版可以打开网页http://paste.ubuntu.com/25534094/。