UOJ958 笔记
模拟赛写了乱搞,结果还挂分了 \(40 \rightarrow 26\)。看题解感觉很 tricky,写一下笔记。
题意
给定一个括号串 \(S\),每个位置有颜色:蓝/绿/红。
你需要改变 \(S\) 一些位置的括号(不改变颜色),使得:
- 整个串是合法括号串。
- 删掉某个颜色后的串也是合法括号串。
\(|S| \le 10^7\)
思路
首先考虑一个合法括号串的判定,自然要满足下面三个条件:
- 任意一个前缀中
(的数量不少于)。 - 任意一个后缀中
)的数量不少于(。 - 整个串中
(、)的数量相等。
以上三个条件中任意两个成立都能说明这个串是合法的。
接着观察题中的信息,可以发现第二个条件蕴含第一个条件,证明:套路地将 ( 看作 \(1\),) 看作 \(-1\)。对于任意前缀 \(i\),设 \(1 \sim i\) 中三色括号的前缀和分别为 \(a, b, c\),则第二个条件等价于 \(a + b \ge 0, a + c \ge 0, b + c \ge 0\),则三者相加得到 \(2(a + b + c) \ge 0\)。后缀也是同理的,于是推出第一个条件。
考虑一个同色的括号串怎么求答案,求法有两种:
- 先满足条件 1,再满足条件 3。
做法:从左往右扫,每次一旦前缀和小于 \(0\) 就将最靠前的)改成(,最后还需要把靠后的(改成)保证条件 3。 - 先满足条件 3,再满足条件 1。
做法:如果(的数量比)少,就先把前几个)改为(;否则把后几个(改为)。
接着依次找到第一个)与最后一个(,将其反转使其满足条件 1。
赛时用了求法 1,但是错的,做不出来,下面说一下为什么求法 1 不对。
错因
先说做法:
可以参考题解的做法写出关于 \(x_1, x_2, x_3\) 的不等式并用线性规划解出。
设 \(x_i\) 表示将多少个颜色为 \(i\) 的 ( 改为了 )。对于一个前缀 \(j\),前缀和会增加 \(\min(x_i, A_{i, j})\),其中 \(A_{i, j}\) 表示有 \(1, 2, \ldots, j\) 中有多少颜色为 \(i\) 的 )。再设 \(B_{i, j}\) 表示 \(1, 2, \ldots, j\) 中颜色为 \(i\) 的括号串的前缀和。合法括号串需要满足(假设删掉的颜色为 \(k\))
在此基础上,最小化
但是我们发现这个式子很不对啊,因为不能保证贪心地改后缀不会影响不等式的成立。
所以理应还有设置 \(y_1, y_2, y_3\),表示后缀将多少个 ( 改为 ),那就是一个 6 元的线性规划,复杂度错了,还很难写,正确性也不保对。(我没继续想了,应该不行)大概是可被题解做法平替的。
接下来说说题解的做法。考虑求法 2,观察到每种颜色内 ( 和 ) 一定相等。证明:设每种颜色中 ( 数量减去 ) 数量的值为 \(a, b, c\),则我们要证 \(a = b = c = 0\),根据题中条件有 \(a + b = b + c = a + c = 0\),那加起来就是 \(2(a + b + c) = 0\),接着减一下就能得到 \(a, b, c\) 都等于 \(0\) 了。
于是可以对每种颜色提前做好第一步:平衡 ( 和 ) 的数量。贪心的把前几个 ) 改成 (,或者把后几个 ( 改成 ) 是最优的。
下面的括号串都是指经过了平衡的括号串。
接下来做第二步,设 \(x_i\) 表示颜色 \(i\) 操作了几次,\(A_{i, j}\) 表示 \([1, j]\) 中有多少颜色为 \(i\) 的 ),\(B_{i, j}\) 表示 \([j + 1, n]\) 中有多少颜色为 \(i\) 的 (,\(C_{i, j}\) 表示 \([1, j]\) 中颜色为 \(i\) 的括号的前缀和。可以写出式子
然后就写成了若干个关于 \(x_1, x_2, x_3\) 的不等式,目标是最小化 \(x_1 + x_2 + x_3\)。
那线性规划就行了,复杂度 \(O(n)\)。
代码:
#include <bits/stdc++.h>
using i64 = long long;
using namespace std;
constexpr int N = 1E7 + 5;
int n, c[N];
string s, t;
int A[3][N], B[3][N], C[3][N];
int X[3], Y[3], R[3];
int ans;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> s >> t;
n = s.size();
s = '#' + s;
t = '#' + t;
array<int, 3> cnt{0, 0, 0};
for (int i = 1; i <= n; ++i) {
c[i] = t[i] - '1';
cnt[c[i]] += (s[i] == '(' ? 1 : -1);
}
for (int j = 0; j < 3; ++j) {
ans += abs(cnt[j]) / 2;
if (cnt[j] < 0) {
for (int i = 1; cnt[j] && i <= n; ++i) {
if (s[i] == ')' && c[i] == j) {
s[i] = '(';
cnt[j] += 2;
}
}
} else if (cnt[j] > 0) {
for (int i = n; cnt[j] && i >= 1; --i) {
if (s[i] == '(' && c[i] == j) {
s[i] = ')';
cnt[j] -= 2;
}
}
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < 3; ++j) {
A[j][i] = A[j][i - 1] + (j == c[i]) * (s[i] == ')');
}
}
for (int i = n; i >= 1; --i) {
for (int j = 0; j < 3; ++j) {
B[j][i] = B[j][i + 1] + (j == c[i]) * (s[i] == '(');
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < 3; ++j) {
C[j][i] = C[j][i - 1] + (j == c[i]) * (s[i] == '(' ? 1 : -1);
}
}
for (int j = 0; j < 3; ++j) {
int nj = (j == 2 ? 0 : j + 1);
for (int i = 1; i <= n; ++i) {
//2x_i + 2min(A[nj][i], B[nj][i]) + C[j][i] + C[nj][i] >= 0
//2x_i + 2x_j + C[j][i] + C[nj][i] >= 0
int l1 = -(2 * min(A[nj][i], B[nj][i]) + C[j][i] + C[nj][i]);
int l2 = -(2 * min(A[j][i], B[j][i]) + C[j][i] + C[nj][i]);
X[j] = max(X[j], (l1 + 1) / 2);
X[nj] = max(X[nj], (l2 + 1) / 2);
Y[j] = max(Y[j], (-C[j][i] - C[nj][i] + 1) / 2);
}
}
int D = 0;
for (int j = 0; j < 3; ++j) {
int nj = (j + 1) % 3;
R[j] = max(0, Y[j] - X[j] - X[nj]);
D = max(D, R[j]);
}
D = max(D, (R[0] + R[1] + R[2] + 1) / 2);
cout << ans + 2 * (D + X[0] + X[1] + X[2]) << "\n";
return 0;
}

浙公网安备 33010602011771号