线段树区间修改
朴素版的线段树只能实现单点修改区间查询,修改和查询的时间复杂度都是O(logn)
懒标记问题
加上懒标记后的线段树就可以实现区间修改了,比如我要把[a,b]的值加上k,肯定不会傻到n次单点修改,是个人都会想到假如递归到一个包含在[a,b]的区间时就不用继续往下递归了,直接告诉这个区间的管理员,把这个区间的和直接给修改了,但是假如你上面第一次增加时增加区间为[c,d],并且区间[c,d]是包含在[a,b]中间的,现在又要在[e,d]增加k,e>c的,这个时候你就会发现[e,d]这个区间增加了两次,因为第一次增加时你根本没有递归到[e,d],而是递归到这个区间的上一层就直接返回了,现在你再在[e,d]区间增加值就会出错,因为少加了第一次的增值,所以应该找一个东西记录下来第一次增值,并且把他带到下面去,这时懒标记就出来了,新开一个数组lazy[i],表示编号为i的区间的累计增值,那么第一次修改[c,d]时,lazyu\就会记录下来增值,lazy[u]+=c,第二次修改经过这个已经修改过的区间时,就要顺带着把这个区间的修改值一起带过去,正所谓父亲欠下的债儿子去偿还,当修改[e,d]时,经过[c,d]区间时,就会把lazy值给带到下面的儿子区间里面
1 | void pushdown(u){ |
这就是区间查询的灵魂了,朴素版和plus只差了几行代码而已,首先修改时判断区间条件变了,只要当前区间处于修改区间内就可以直接修改,修改时把区间值增加区间长度*增值,并把加法标记lazy累加即可
线段树1
一道模板题线段树1
1 |
|
线段树2
接下来看一道比较难的升级版本线段树2
这道题只是增加了一个操作,不只可以给一段区间增加值,也可以乘上一个值,假如两种运算分开考的话那么这道题就是一道模板题,但难就难在他一起考了,你一定会想到这就会涉及到优先级问题,因为假如给一段区间增加了一个值,又给这段区间乘上一个值,之后递归时经过这个区间时,你就弄不清楚应该先加还是先乘,而这两种结果显然是不同的,假设儿子区间的值为a,父亲区间已经有了加法标记b和乘法标记c,那么两种不同的运算顺序会得到不同结果
$(a+b)c!=ac+b$
那么有没有办法规定一种固定的运算顺序,使得无论是先给父亲区间加上加法标记还是乘法标记最终得到相同的结果呢?
答案是有的,观察上面不等式两边的式子发现只要在右面的b后面乘上一个c两个式子就相等了,因此我们就可以规定一种运算,永远先乘再加,当添加标记的顺序就是先乘再加时肯定没问题,但是若先添加的加法标记然后添加的乘法标记时怎么办?这时候计算儿子的值时就是儿子的值*乘值+儿子区间长度*增值
,假如先加后乘那么儿子的值应该是(儿子的值+儿子区间长度*增值)*乘值
,发现了吗?实际和理想只差了一个乘法标记,所以当先添加加法标记再添加乘法标记时,把已经添加的加法标记乘上乘值即可使得两种不同的顺序得到相同的结果,儿子的加法标记更新时就要先乘以父亲乘法标记再加上父亲加法标记,你可能会问为什么有这种情况,向下传递时父亲标记不是被传递给儿子了吗?但传递后还可以再给啊!传递后我再让父亲区间加上或者乘上一个值,这也是合法的,所以当再次经过父亲区间时儿子区间的加法标记还没有乘上父亲的增值,就需要补充上去,而乘法标记只需要乘以父亲乘法标记即可
核心代码
1 | void pushdown(ll u){ |
为什么不先加后乘呢?因为这样就涉及到了除法运算,就出现了精度问题,会出错。
ACCODE
1 |
|