Luogu P7914 [CSP-S 2021] 括号序列 题解 [ 蓝 ] [ 区间 DP ] [ 前缀和优化 ] [ 调试技巧 ] [ 模拟 ]
括号序列:无聊,感觉做过类似的拼接类区间 DP 就直接秒了。
注意到这个超级括号序列定义很复杂,除了合法序列的两边没有 \(\texttt{*}\) 以外,没有啥很好的性质。于是直接考虑暴力区间 DP:定义 \(dp_{l, r}\) 表示 \(l\sim r\) 的合并方案数。
因为是拼接类区间 DP,所以还需要定义一个辅助转移数组 \(g_{l, r}\),表示将 \(l\sim r\) 合成到一个括号内的方案数。
剩下的就是些无聊的转移了,我们根据题面上的定义分别计数:
- 规则 \(1\):
- 直接枚举区间预处理 \(dp, g\) 的初值即可。
- 规则 \(3\):需要特判区间两边是否能被设为括号。
(A):从 \([l + 1, r - 1]\) 处转移 \(dp, g\) 即可。(SA):枚举左侧的S转移 \(dp, g\)。(AS):同理。
- 规则 \(2\):
AB:拼接类区间 DP 板子,将转移划分为拼接左侧的整段,利用 \(g\) 数组转移即可。ASB:同理,只是多了个后缀和优化而已。
直接 DP,时间复杂度 \(O(n^3)\)。
一个计数题调试小技巧 from Lucyna_Kushinada,让我们膜拜他!!!1:
在 DP 转移的时候记录每个 DP 值存的方案,重载运算符并利用
vector转移。可以比较直观地调试重复和漏掉的计数。
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
using pi = pair<int, int>;
const int N = 505;
const ll mod = 1e9 + 7;
ll n, m, dp[N][N], suf[N], mxlen[N], g[N][N];
char s[N];
void add(ll &x, ll v)
{
x += v;
if(x >= mod) x -= mod;
}
int main()
{
// freopen("bracket.in", "r", stdin);
// freopen("bracket.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> s + 1;
// 预处理每个 DP 的初始值、每个位置的最长延伸值,即第一条规则
for(int i = 1; i <= n; i++)
{
for(int j = i + 1; j <= n; j++)
{
bool legal = 1;
if(!(s[i] == '(' || s[i] == '?')) legal = 0;
if(!(s[j] == ')' || s[j] == '?')) legal = 0;
for(int k = i + 1; k < j; k++)
if(!(s[k] == '*' || s[k] == '?'))
legal = 0;
if(j - i + 1 - 2 > m) legal = 0;
dp[i][j] = legal;
g[i][j] = legal;
}
mxlen[i] = i - 1;
for(int j = i; j <= n; j++)
{
if(!(s[j] == '*' || s[j] == '?')) break;
if(j - i + 1 > m) break;
mxlen[i] = j;
}
}
// 进行转移
for(int len = 3; len <= n; len++)
{
for(int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
if((s[l] == '(' || s[l] == '?') && (s[r] == ')' || s[r] == '?'))
{
// (A)
add(dp[l][r], dp[l + 1][r - 1]);
add(g[l][r], dp[l + 1][r - 1]);
// (SA)
bool legal = 1;
for(int i = l + 1; i <= r - 2; i++)
{
legal &= (s[i] == '*' || s[i] == '?');
legal &= (i - l <= m);
if(!legal) break;
add(dp[l][r], dp[i + 1][r - 1]);
add(g[l][r], dp[i + 1][r - 1]);
}
// (AS)
legal = 1;
for(int i = r - 1; i >= l + 2; i--)
{
legal &= (s[i] == '*' || s[i] == '?');
legal &= (r - i <= m);
if(!legal) break;
add(dp[l][r], dp[l + 1][i - 1]);
add(g[l][r], dp[l + 1][i - 1]);
}
}
// AB,要求 A 为整段
for(int i = l + 1; i <= r - 1; i++)
add(dp[l][r], g[l][i] * dp[i + 1][r] % mod);
// ASB,要求 A 为整段
memset(suf, 0, sizeof(suf));
for(int i = r; i >= l; i--)
{
suf[i] = suf[i + 1];
add(suf[i], dp[i][r]);
}
for(int i = l + 1; i <= r - 2; i++)
{
int rx = mxlen[i + 1];
if(rx == i) continue;
rx = min(rx, r - 1);
ll tmp = g[l][i] * (suf[i + 2] - suf[rx + 2]) % mod;
tmp = ((tmp % mod) + mod) % mod;
add(dp[l][r], tmp);
}
}
}
cout << dp[1][n];
return 0;
}

浙公网安备 33010602011771号