[NOIp模拟赛][动态规划] 简单的序列

给定一个括号序列 \(s\),满足 \(|s| = m\)。你需要统计括号序列对 \((p,\ q)\) 的数量。
其中 \((p,\ q)\) 满足 \(|p| + |s| + |q| = n\),且 \(p + s + q\) 是一个合法的括号序列。


惨痛教训:不要形成思维定势!!!

一看到括号序列,满脑子就往栈去了,然后啥也没想出来就爆蛋了……

实际上,我们可以将整个括号序列抽象为如下形式:

\[(\ \rightarrow\ 1 \\ )\ \rightarrow\ -1 \]

那么一个合法的括号序列要保证:

  1. 前缀和为 \(0\)
  2. 每一部分的前缀和不小于 \(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;
}

切记:不要形成思维定势!!!

posted @ 2020-09-27 18:58  ChPu437  阅读(160)  评论(0编辑  收藏  举报