题解:CF1264D Beautiful Bracket Sequence
太笨了,其他题解写都没写的性质都看不出来……
Solution
easy version
我们考虑没有 ?
的串怎么做。由于能无限删,留下 (((...)))
这种肯定不劣。记 \(a_i\) 为 \([1,i]\) 的 (
数,\(b_i\) 为 \((i,n]\) 的 )
数,答案即为 \(\max_{i\in[1,n)}\min(a_i,b_i)\)。
直接不太好做,最多也就是 \(\mathcal{O}(n^3)\) 的 DP。我们需要找一些性质。
性质 1:随着 \(i\) 变大,\(a_i\) 不降,\(b_i\) 不升,\(a_i-b_i\) 上升。
较为显然。
性质 2:对于任意括号串,有且仅有一个位置满足 \(\min(a_i,b_i)\) 取到 \(\max\) 且 \(a_i=b_i\)。
证明:
- 随意找一个 \(\min(a_i,b_i)\) 取到 \(\max\) 的位置 \(p\)。令 \(x_p\) 为 \(a_p-b_p\)。
- 若 \(x_p>0\),令 \(p\gets p-1\),根据性质 1,\(x_p\) 减小;
- 若 \(x_p<0\),令 \(p\gets p+1\),根据性质 1,\(x_p\) 增大。
- 重复执行以上步骤直至 \(x_p=0\)。此时即 \(a_p=b_p\)。
- 因为 \(x_i-x_{i-1}=1,x_0\le 0,x_n\ge 0\),总能找到 \(x_p=0\) 的位置。
- 随着移动变化的仅为 \(a_i,b_i\) 中较大者(若不是,就有更优的答案),\(\min(a_i,b_i)\) 不变。
我们称上面提到的位置为「中间位」。
记 \(a_i\) 为 \([1,i]\) 未填时的 (
数,\(b_i\) 为 \((i,n]\) 未填时的 )
数,\(x_i\) 为 \([1,i]\) 的 ?
数,\(y_i\) 为 \((i,n]\) 的 ?
数。我们枚举「中间位」\(i\) 与答案 \(j\) 来计算,需要保证左边恰好有 \(j\) 个 (
,右边恰好有 \(j\) 个 )
,答案即为:
复杂度 \(\mathcal{O}(n^2)\),可以通过 easy version。
typedef Mint<mod> MI;
int n, x, y, a, b; MI rs; Comb<MI> C; string s;
int main() {
cin >> s, n = s.size(), s = "#" + s, C.init(n);
REP(i, 1, n) {
if (s[i] == '?') y ++;
if (s[i] == ')') b ++;
}
REP(i, 1, n) { // i=0 时贡献必为 0,可以省略
if (s[i] == '(') a ++;
if (s[i] == '?') x ++, y --;
if (s[i] == ')') b --;
REP(j, 0, n)
rs += C(x, j - a) * C(y, j - b) * j;
}
cout << rs << '\n';
return 0;
}
hard version
对于硬版本,我们沿用前面的思路,考虑化简式子。
前面的 \(\sum_{i=0}^{n}\) 很难化简,我们化简后面的那个求和。\(j\) 这个系数很讨厌,可以将其化为 \((j-a_i)+a_i\),然后想办法塞进组合数里。
\(m\binom{n}{m}\) 的化简方法:\(m\binom{n}{m}=\frac{n!m}{m!(n-m)!}=\frac{(n-1)!n}{(m-1)!(n-m)!}=n\binom{n-1}{m-1}\)。
范德蒙特卷积:\(\sum_{i=0}^{n}\binom{n}{i}\binom{m}{k-i}=\binom{n+m}{k}\)。我们接下来利用它来化简。
解释一下第三步,我们从枚举 \(j\) 变为枚举 \((j-b_i)\),因为原来 \(j\) 超出上下界时后面的项乘积为 \(0\),所以范围不用特意改变。
同样的方法化简前面的:
答案化简为
时间复杂度 \(\mathcal{O}(n)\),可以通过 hard ersion。
typedef Mint<mod> MI;
int n, x, y, a, b; MI rs; Comb<MI> C; string s;
int main() {
cin >> s, n = s.size(), s = "#" + s, C.init(n);
REP(i, 1, n) {
if (s[i] == '?') y ++;
if (s[i] == ')') b ++;
}
REP(i, 1, n) { // i=0 时贡献必为 0,可以省略
if (s[i] == '(') a ++;
if (s[i] == '?') x ++, y --;
if (s[i] == ')') b --;
rs += C(x + y, x + a - b) * a + C(x + y - 1, x + a - b) * x;
}
cout << rs << '\n';
return 0;
}