简单序列
题目链接:ybtoj高效进阶 21171
题目大意
给你一个括号序列,然后再给一个 n。
要你找有多少个括号序列对 (a,b),使得三个括号序列的长度和是 n,而且把给你的括号序列左边拼上 a,右边拼上 b,它是一个合法的括号序列。
思路
发现两边要补上的其实很少,只有 2000 2000 2000,考虑可以 ( n − m ) 2 (n-m)^2 (n−m)2 的做法
考虑 DP,设
f
i
,
j
f_{i,j}
fi,j 为搞定
i
i
i 的长度,然后左括号的剩余数量是
j
j
j。(当然这个过程中,左括号数量不能是负)
如果是右边,就是右括号的,不过两个的 DP 方式一样,所以可以只用一个
f
f
f。
其实就是
f
i
,
j
=
f
i
−
1
,
j
−
1
+
f
i
−
1
,
j
+
1
f_{i,j}=f_{i-1,j-1}+f_{i-1,j+1}
fi,j=fi−1,j−1+fi−1,j+1,如果
j
=
0
j=0
j=0 就特殊一点,
f
i
,
0
=
f
i
−
1
,
1
f_{i,0}=f_{i-1,1}
fi,0=fi−1,1。
然后初始化
f
0
,
0
=
1
f_{0,0}=1
f0,0=1。
然后你考虑枚举两边的长度。
那我们就要求出中间的那个部分,最后多了多少个左括号(可能是负数),以及最少的时候有多少个左括号(一定
⩽
0
\leqslant 0
⩽0)。
要求最小的是因为中间搞的过程中也不能出现是负数的。
然后就再枚举前面那个最后有多少个左括号。(这里的数量要大于等于中间最少的时候)
然后也可以算出右边要最后多少个左括号。(因为要最后的时候不多不少,就是
0
0
0)
然后枚举的两边结果乘起来,每次的结果和就是答案了。
代码
#include<cstdio>
#include<iostream>
#define ll long long
#define mo 1000000007
using namespace std;
int n, m;
char s[100001];
int les, noww;
ll f[2001][2001], ans;
int main() {
// freopen("read.txt", "r", stdin);
// freopen("bracket.in", "r", stdin);
// freopen("bracket.out", "w", stdout);
scanf("%d %d", &n, &m);
scanf("%s", s + 1);
noww = 0;
for (int i = 1; i <= m; i++) {
if (s[i] == '(') noww++;
else noww--;
les = min(les, noww);
}
int q = n - m;
f[0][0] = 1;
for (int i = 1; i <= q; i++)//DP
for (int j = 0; j <= i; j++) {
if (j < i) f[i][j] += f[i - 1][j + 1];//加左括号
if (j) f[i][j] += f[i - 1][j - 1];//加右括号
f[i][j] %= mo;
}
for (int i = 0; i <= q; i++) {
for (int j = 0; j <= i; j++)//左边加上中间最少的时候也不能是负数
if (j + les >= 0 && j + noww <= q - i)//右边也要有可能有那么多个
ans = (ans + f[i][j] * f[q - i][j + noww] % mo) % mo;
}
printf("%lld", ans);
return 0;
}