【区间dp】P7914 [CSP-S 2021] 括号序列

题意

  1. ()、(S) 均是符合规范的超级括号序列,其中 S 表示任意一个仅由不超过 k k k个字符 ∗ * 组成的非空字符串(以下两条规则中的 S 均为此含义);
  2. 如果字符串 A 和 B 均为符合规范的超级括号序列,那么字符串 AB、ASB 均为符合规范的超级括号序列,其中 AB 表示把字符串 A 和字符串 B 拼接在一起形成的字符串;
  3. 如果字符串 A 为符合规范的超级括号序列,那么字符串 (A)、(SA)、(AS) 均为符合规范的超级括号序列。

所有符合规范的超级括号序列均可通过上述 3 条规则得到。

现在给出一个长度为 n n n的超级括号序列,一些位置的字符尚未确定(用 ? 表示)。求有多少种方法,使得得到的字符串是一个符合规范的超级括号序列?
1 ≤ k ≤ n ≤ 500 。 1 \le k \le n \le 500。 1kn500

思路

f l , r f_{l,r} fl,r l ∼ r l \sim r lr符合规范的序列方案, S l , r S_{l,r} Sl,r l ∼ r l \sim r lr是否可以拼成一个 S S S
第一种情况以及第三种情况中的(A),直接判断两端是否可以为括号,中间是否可以为 S S S
第三种情况,枚举断点即可。
对于第二种情况,需要枚举 S S S,发现 S S S的左端点是随着右端点向右移而右移的,可以用前缀和优化。

注意到ASASA,这种情况会被计算2遍,所以第二种分开计算即可。

时间复杂度 O ( N 3 ) O(N^3) O(N3)

代码

#include 
#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);
}

你可能感兴趣的:(动态规划,OI,csp2021,c++)