题目
题目描述
定义只由 (
和 )
组成的序列为括号序列。
定义一个括号序列是合法的,当且仅当它的任意一个前缀都满足 (
的数量不少于 )
的数量,且整个序列中 (
和 )
的数量是相等的。
定义一个括号序列是 “好的”,当且仅当存在两个子序列,满足二者的并集为原序列(但不必不重叠),并且每个子序列都是合法的括号序列。
现在,给定一个长为 n n n 的括号序列。有 m m m 次询问,每次询问给定一个区间 [ L , R ] [L,R] [L,R],你需要求出 [ L , R ] [L,R] [L,R] 中有多少个子区间是 “好的” 括号序列。
数据范围与提示
n
,
m
≤
3
×
1
0
5
n,m≤3\times 10^5
n,m≤3×105,
1
≤
L
≤
R
≤
n
1≤L≤R≤n
1≤L≤R≤n 。
思路
考虑暴力检查每个子区间。有一个挺不错的贪心思路:为了避免右括号过多,遇到左括号就加入两个子序列,遇到右括号就只加入一个子序列。如果过程中没有左括号不够用的情况,那就只可能有左括号剩余。下文中,都用 a a a 表示左括号数量,用 b b b 表示右括号数量。
最初我认为,只要左括号的剩余不超过
a
a
a 即可,也就是可以退还一些 (
。但是 )
也是可以加的啊!那就不是
≤
a
+
b
\le a+b
≤a+b 这么简单了,因为此时 )
的位置同样重要。(我就是被 ())(
轻松的
h
a
c
k
\rm hack
hack 掉的。)
其实,只要 把原序列翻转(翻转时会让 (
和 )
互换)再做一遍就行了。翻转前,保证了右括号不会太多。翻转后,保证了左括号不会过多。——这是感性的证明。理性的证明如下:只要找到一个分界点,使得前缀的
2
a
1
−
b
1
2a_1-b_1
2a1−b1 等于后缀的
2
b
2
−
a
2
2b_2-a_2
2b2−a2,就可以前缀按照第一遍的贪心填,后缀按照翻转后的贪心填。
假设它不成立。当
i
=
0
i=0
i=0 时,前缀的
2
a
1
−
b
1
=
0
2a_1-b_1=0
2a1−b1=0 所以
2
b
2
−
a
2
>
0
2b_2-a_2>0
2b2−a2>0 。同理,
i
=
n
i=n
i=n 时
2
a
1
−
b
1
>
0
2a_1-b_1>0
2a1−b1>0 。故而
2
a
1
−
b
1
2a_1-b_1
2a1−b1 与
2
b
2
−
a
2
2b_2-a_2
2b2−a2 的大小关系有过变化。而二者的差值的变化量是
±
1
\pm 1
±1(分类讨论一下当前是 (
还是 )
即可知),所以过程中必然相交。相交则有解。
从证明中跳出来——我们已经知道,子区间是 “好的” 的充要条件了,即前缀和 ≥ 0 \ge 0 ≥0 与后缀和 ≥ 0 \ge 0 ≥0 。不妨先看一看前缀和如何维护。
从小到大枚举右端点,线段树维护前缀和的区间最小值,修改时,如果一个区间的最小值 < 0 <0 <0 了,就递归往下来删除它。由于每个点做多被删除一次,总复杂度还是 O ( n log n ) \mathcal O(n\log n) O(nlogn) 的。
然后考虑后缀和如何维护。这个可以对于每个点预处理 L i L_i Li 为,使得 [ L i , i ] [L_i,i] [Li,i] 的所有后缀和都 ≥ 0 \ge 0 ≥0 的最小 L i L_i Li 。用单调栈预处理——本质是,求前缀和第一个比自己大的。
最后一个问题:怎么统计答案?就是这个问题把我一下子难倒了。我还觉得需要用可持久化的黑科技,将很多线段树给 “重叠” 在一起。其实这个问题还是思维定式,我始终觉得需要在 r r r 这里计算答案。其实,权值可以放到左端点上。线段树上打一个标记,表示 “没有被删除的 l l l 对应的答案加一”,实际含义就是,对于每个 l l l 存储了 ∀ r ∈ [ l , R ] \forall r\in[l,R] ∀r∈[l,R] 有多少个 “好的” 子区间 [ l , r ] [l,r] [l,r] 。然后询问就是 区间查询 了。
复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn),只有一个线段树。