[NOIp模拟赛][动态规划] 简单的序列
给定一个括号序列 \(s\),满足 \(|s| = m\)。你需要统计括号序列对 \((p,\ q)\) 的数量。
其中 \((p,\ q)\) 满足 \(|p| + |s| + |q| = n\),且 \(p + s + q\) 是一个合法的括号序列。
惨痛教训:不要形成思维定势!!!
一看到括号序列,满脑子就往栈去了,然后啥也没想出来就爆蛋了……
实际上,我们可以将整个括号序列抽象为如下形式:
\[(\ \rightarrow\ 1 \\
)\ \rightarrow\ -1
\]
那么一个合法的括号序列要保证:
- 前缀和为 \(0\)
- 每一部分的前缀和不小于 \(0\)
然后我们可以设 \(f_{i,j}\) 表示长度为 \(i\) 的括号序列前缀为 \(j\) 的合法情况有多少个,然后一个非常简单的 \(DP\) 就能解决问题了。
代码:
# include <iostream>
# include <cstdio>
# define MAXN 100005
# define MAXM 2005
const long long MOD = 1e9+7;
char s[MAXN];
long long f[MAXM][MAXM];
// f[i][j] 长度为 i,前缀和为 j
// ( -> 1 ) -> -1
// 合法序列:j = 0,每个前缀和都不小于 0
int main(){
int n, m;
scanf("%d%d", &n, &m);
scanf("%s", s+1);
f[0][0] = 1;
for(int i = 1; i <= n-m; i++){
for(int j = 0; j <= i; j++){
if(j > 0){
f[i][j] += f[i-1][j-1];
if(f[i][j] > MOD){
f[i][j] -= MOD;
}
}
if(j < i-1){
f[i][j] += f[i-1][j+1];
if(f[i][j] > MOD){
f[i][j] -= MOD;
}
}
}
}
int cur = 0, sum = 0;
for(int i = 1; i <= m; i++){
cur += s[i] == '(' ? 1 : -1;
sum = std::min(sum, cur);
}
long long ans = 0;
for(int i = -sum; i <= n-m; i++){
for(int j = -sum; j <= i; j++){
if(cur + j <= n-m-i){
ans = (ans + f[i][j] * f[n-m-i][cur+j]) % MOD;
}
}
}
printf("%lld", ans);
return 0;
}