题意
- ()、(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k k k个字符 ∗ * ∗组成的非空字符串(以下两条规则中的 S 均为此含义);
- 如果字符串 A 和 B 均为符合规范的超级括号序列,那么字符串 AB、ASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
- 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)、(SA)、(AS) 均为符合规范的超级括号序列。
所有符合规范的超级括号序列均可通过上述 3 条规则得到。
现在给出一个长度为
n
n
n的超级括号序列,一些位置的字符尚未确定(用 ? 表示)。求有多少种方法,使得得到的字符串是一个符合规范的超级括号序列?
1
≤
k
≤
n
≤
500
。
1 \le k \le n \le 500。
1≤k≤n≤500。
思路
设
f
l
,
r
f_{l,r}
fl,r为
l
∼
r
l \sim r
l∼r符合规范的序列方案,
S
l
,
r
S_{l,r}
Sl,r为
l
∼
r
l \sim r
l∼r是否可以拼成一个
S
S
S。
第一种情况以及第三种情况中的(A),直接判断两端是否可以为括号,中间是否可以为
S
S
S。
第三种情况,枚举断点即可。
对于第二种情况,需要枚举
S
S
S,发现
S
S
S的左端点是随着右端点向右移而右移的,可以用前缀和优化。
注意到ASASA,这种情况会被计算2遍,所以第二种分开计算即可。
时间复杂度 O ( N 3 ) O(N^3) O(N3)
代码
#include <cstdio>
#define int long long
const int mod = 1e9 + 7;
int n, lim;
char s[511];
int f[511][511], S[511][511], g[511][511];
signed main() {
scanf("%lld %lld", &n, &lim);
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) {
S[i][i - 1] = 1;
for (int j = i; j - i + 1 <= lim && j <= n; j++)
S[i][j] = S[i][j - 1] & (s[j] == '*' || s[j] == '?');
}
for (int i = n; i >= 1; i--)
for (int j = i; j <= n; j++) {
if ((s[i] == '(' || s[i] == '?') && (s[j] == ')' || s[j] == '?')) {
(f[i][j] += f[i + 1][j - 1] + S[i + 1][j - 1]) %= mod;//第一种
for (int k = i + 1; k < j - 1; k++)
(f[i][j] += f[i + 1][k] * S[k + 1][j - 1] + f[k + 1][j - 1] * S[i + 1][k]) %= mod;//第三种
}
g[i][j] = f[i][j];//去重
int sum = 0, l = i;
for (int r = i + 1; r < j; r++) {//第二种
(sum += f[i][r]) %= mod;
while (!S[l][r]) {
(sum -= f[i][l]) %= mod;
l++;
}
(f[i][j] += (sum + f[i][l - 1]) * g[r + 1][j]) %= mod;
}
}
printf("%lld", (f[1][n] + mod) % mod);
}