feat: 添加超级括号序列问题的动态规划解法

实现基于区间DP的解决方案,处理四种不同状态转移:
1. 任意合法序列的计数
2. 前缀星号序列的计数
3. 被括号包裹序列的计数
4. 后缀星号序列的计数

包含预处理步骤快速判断子串是否可全为星号,使用模运算防止溢出。
This commit is contained in:
Zengtudor 2025-09-25 20:26:46 +08:00
parent 66f957900f
commit 929a5ed188

126
src/9/25/P7914.cpp Normal file
View File

@ -0,0 +1,126 @@
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
// 使用 long long 防止中间结果溢出
using ll = long long;
const int MOD = 1e9 + 7;
// DP 状态数组
ll f[505][505]; // f[i][j]: S[i..j] 构成一个任意合法超级括号序列的方案数
ll g[505][505]; // g[i][j]: S[i..j] 构成 "SA" 形式S 为前缀 * 串)的方案数
ll h[505][505]; // h[i][j]: S[i..j] 构成 A 类(被括号包裹)序列的方案数
ll f_suff_S[505][505]; // f_suff_S[i][j]: S[i..j] 构成 "AS" 形式S 为后缀 * 串)的方案数
// 预处理数组
bool is_star[505][505]; // is_star[i][j]: S[i..j] 是否能全变成 '*'
int main() {
// 使用 OI/ACM 风格的快速 I/O
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, k;
cin >> n >> k;
string s;
cin >> s;
// 为了方便,我们将字符串和 DP 数组都使用 1-based 索引
string S = " " + s;
// --- 预处理 is_star[i][j] ---
// 一个子串 S[i..j] 能否全为 '*',取决于它是否包含 '(' 或 ')'
// 我们可以用前缀和 O(1) 查询
vector<int> prefix_L(n + 2, 0), prefix_R(n + 2, 0);
for (int i = 1; i <= n; ++i) {
prefix_L[i] = prefix_L[i - 1] + (S[i] == '(');
prefix_R[i] = prefix_R[i - 1] + (S[i] == ')');
}
for (int i = 1; i <= n; ++i) {
for (int j = i; j <= n; ++j) {
if ((prefix_L[j] - prefix_L[i - 1] == 0) && (prefix_R[j] - prefix_R[i - 1] == 0)) {
is_star[i][j] = true;
}
}
}
// --- 区间 DP 主循环 ---
// len 是当前处理的子串长度
for (int len = 2; len <= n; ++len) {
for (int i = 1; i <= n - len + 1; ++i) {
int j = i + len - 1;
// --- 1. 计算辅助状态 g[i][j] 和 f_suff_S[i][j] ---
// 这两个状态的计算依赖于 f[...][...],而 f 的值在更小的 len 时已经算好
// g[i][j] (SA form): S[i..p] as S, S[p+1..j] as A
for (int p = i; p <= min(j - 1, i + k - 1); ++p) {
if (is_star[i][p]) {
if (p + 1 <= j) {
g[i][j] = (g[i][j] + f[p + 1][j]) % MOD;
}
}
}
// f_suff_S[i][j] (AS form): S[i..p] as A, S[p+1..j] as S
for (int p = max(i, j - k); p < j; ++p) {
if (is_star[p + 1][j]) {
f_suff_S[i][j] = (f_suff_S[i][j] + f[i][p]) % MOD;
}
}
// --- 2. 计算 A 类序列 h[i][j] ---
if ((S[i] == '(' || S[i] == '?') && (S[j] == ')' || S[j] == '?')) {
int i_inner = i + 1;
int j_inner = j - 1;
// Base case: "()"
if (len == 2) {
h[i][j] = 1;
} else if (i_inner <= j_inner) {
int len_inner = j_inner - i_inner + 1;
// (S) form: inner part is all '*', length must be 1 to k
if (len_inner <= k && is_star[i_inner][j_inner]) {
h[i][j] = (h[i][j] + 1) % MOD;
}
// (A) form: inner part is a valid SBS
h[i][j] = (h[i][j] + f[i_inner][j_inner]) % MOD;
// (SA) form: inner part is S then A
h[i][j] = (h[i][j] + g[i_inner][j_inner]) % MOD;
// (AS) form: inner part is A then S
h[i][j] = (h[i][j] + f_suff_S[i_inner][j_inner]) % MOD;
}
}
// --- 3. 计算任意合法序列 f[i][j] ---
// 任意合法序列可以被唯一分解为 `A' R`
// `A'` 是第一个 A 类子序列 S[i..p], `R` 是剩余部分 S[p+1..j]
// Case 1: 整个 S[i..j] 就是一个 A 类序列 (R 为空)
f[i][j] = h[i][j];
// Case 2: S[i..p] 是第一个 A 类序列S[p+1..j] 是非空剩余部分
for (int p = i; p < j; ++p) {
if (h[i][p] == 0) continue;
// R 可以是 'B' (AB form) 或 'SB' (ASB form)
// 方案数总和为 f[p+1][j] + g[p+1][j]
ll ways_A = h[i][p];
ll ways_R = (f[p + 1][j] + g[p + 1][j]) % MOD;
f[i][j] = (f[i][j] + ways_A * ways_R) % MOD;
}
}
}
cout << f[1][n] << endl;
}