cf div2 1082 E (子序列计数 dp)
E. Rigged Bracket Sequence
一道稍稍灵活的子序列计数 dp,补了一晚上才补出来qwq。。。
Q: 给定长度为 \(n\) 的 RBS,对子序列计数,子序列需满足条件:循环右移一位后,整个括号串仍是 RBS。
一个括号串是 RBS:
- 末尾项 \(pre\) 等于 \(0\)
- 前缀所有位置的 \(pre \geq 0\)
显然循环右移操作不会破坏条件1;而对于任意以左括号结尾的子序列,循环右移后条件2一定仍然满足。故我们只需要考虑所有以右括号结尾的子序列。
子序列可行,等价于操作后,每个位置的 \(pre\) 值仍 \(\geq 0\)。
考虑对某个子序列进行该操作会对哪些位置的 \(pre\) 产生什么样的影响:显然一定不会对子序列两侧的位置产生影响;对于子序列内部(子序列两个端点构成的区间)的任何一个位置,考虑在子序列的所有位置中位于其左侧的最近的那个位置:
- 若为左括号,则该位置的 \(pre\) 值会减 \(2\)
- 若为右括号,则该位置的 \(pre\) 值不变
证明:考虑该位置前面的所有括号,经过循环右移操作后,变化的括号只有两个:
- 新加入了子序列的最后一个括号(这里一定是右括号,因为只考虑所有以右括号结尾的子序列)
- 移除了该位置左侧最近的、在子序列中的那个位置上的括号
于是,一个子序列是否可行,我们有了一个较为清晰的判断方法。考虑如何 dp:
状态定义
\(dp_{i}\):强制 \(s[i]\) 为右括号(即使 \(s[i]\) 是左括号,也只是统计该位置是右括号时的情况);\(s[i]\) 钦定为子序列的结尾,考虑往前添加 \(s[1 \backsim i - 1]\),方案数。
(个人认为这样的状态定义好难想qwq。。。)
状态转移
考虑 \(dp_{i} \leftarrow dp_{j}\),\(j \in [1, i - 1]\)。
- 若 \(s[j]\) 是右括号,则对于所有以 \(s[j]\) 结尾的合法子序列,加入 \(s[i]\) 后再进行循环右移操作,与对原来的子序列做循环右移操作是完全等价的,故一定仍合法。
- 否则,\(s[j]\) 是左括号,在添加 \(s[i]\) 并做循环右移后,与 \(dp_{j}\) 表示的情况相比(注意对于 \(dp_{j}\),\(s[j]\) 是右括号),唯一的变化在于:\(pre[j\backsim i - 1]\) 均减 \(2\)(前面多了一个刚加入的右括号 \(s[i]\),少了一个左括号 \(s[j]\))。所以若想让该情况合法,必须满足 \(pre[j\backsim i - 1]\) 均 \(\geq 2\)。
因此,计算 \(dp_{i}\) 时,我们可以对每个位置 \(i\) 预处理出 \(pre < 2\) 的最左侧位置 \(lst\),对于 \(j \leq lst\),我们只转移 \(s[j]\) 是右括号的 \(dp_{j}\);对于 \(j > lst\),由于一定满足 \(pre(lst,i-1] \geq 2\),故这个区间的每个位置的 \(dp_{j}\) 都可以转移。剩下的就是经典的前缀和优化 dp 了,具体实现见 code。

浙公网安备 33010602011771号