题意简述
你有一个长度为的01序列,支持个操作,操作分五种,形式分别是:
0 l r
从到都变成1 l r
从到都变成2 l r
从到全部取反(变成,变成)3 l r
询问到有多少4 l r
询问到最长的连续的有多长
思路框架
线段树多标记。
具体思路
线段树,分几个方面讲
- 每个节点存什么信息
- 如何维护这些信息(
Update
函数) lazytag
的优先级,以及如何单节点修改,还有如何PushDown
。
节点信息
我们按操作顺序考虑。首先是区间覆盖的操作,那么我们就要维护一个标记,设为 标记(简记为 )。由于 取 都是有意义的,那么就只好用 表示没有标记了。
然后还有取反操作。那么我们维护一个 标记(简记为 ), 表示区间被取反了 次。由于取反两次就相当于没有取反,所以 只需要设置为 即珂。
然后询问有多少个 。显然我们需要维护区间有多少个 ,设为 ( 就是 啦)。然后我们发现,取反完之后, 变成 , 变成。所以有的标记, 都要有。所以我们还有维护区间有多少 ,设为 。
然后询问连续的 。这个是套路:我们维护从左边起最大连续的 ,设为 (就是left consecutive
的简写 ),还有从右边起最大连续的 ,设为 。还有最长的连续 ,记为。(这个名字是瞎jb取的,因为没有名字了)
如何维护
众所周知,update
的时候,lazytag
是不重要的。我们直接将其设置为空标记即珂。
显然, 就直接左子树+右子树即珂。
就先设置成左子树的 。如果发现它占满了整个左子树,那就要跨越到右子树去了。此时再加上右子树的 即珂。
同理。
然后 (最长连续的 )的维护也很容易,就是左子树连续的 ,右子树连续的 ,还有左子树的右连续()+右子树的左连续(),三个取 。
(实现小技巧:我们珂以把节点打个 ,然后封装一个加号)
先放个代码上来,方便理解
1 | struct node{int l,r,f,c,lc[2],rc[2],s[2],x[2];}; |
lazytag的优先级&如何维护lazytag
单区间修改
覆盖操作:显然,覆盖的优先级绝对大于取反,所以先令 。接着,假设我们都覆盖上了,。然后 都等于区间长度,而另一个颜色( 取反)的 都等于。
如果是取反的操作,因为是低优先级的操作,所以直接修改 标记即珂。由于取反完之后, 变成 , 变成 ,所以我们只需要把 的 两维交换一下即珂。
PushDown
PushDown
的时候,先操作 标记,再操作 标记。我们下传标记的时候,只对儿子作修改,而不对本身作修改(因为在 PushDown
前面一次单区间修改的时候,应该已经改过了)。所以如果遇到“先覆盖后翻转”的情况,就不会错误地把 标记删掉了。而两个儿子也是 标记先到, 后到,所以儿子节点的 标记也不会被错删掉。
一个小问题:如何写最长连续1的Query函数(代码中的QueryLen)
Query
函数返回一个线段树节点即珂。最后取节点的 属性,就是最长连续 的长度了。
完整代码(超长)
1 |
|