题目链接
题目描述
小 w 在赛场上遇到了这样一个题:一个长度为 n n n 且符合规范的括号序列,其有些位置已经确定了,有些位置尚未确定,求这样的括号序列一共有多少个。
身经百战的小 w 当然一眼就秒了这题,不仅如此,他还觉得一场正式比赛出这么简单的模板题也太小儿科了,于是他把这题进行了加强之后顺手扔给了小 c。
具体而言,小 w 定义“超级括号序列”是由字符 (
、)
、*
组成的字符串,并且对于某个给定的常数
k
k
k,给出了“符合规范的超级括号序列”的定义如下:
()
、(S)
均是符合规范的超级括号序列,其中S
表示任意一个仅由不超过 k \bm{k} k 个字符*
组成的非空字符串(以下两条规则中的S
均为此含义);- 如果字符串
A
和B
均为符合规范的超级括号序列,那么字符串AB
、ASB
均为符合规范的超级括号序列,其中AB
表示把字符串A
和字符串B
拼接在一起形成的字符串; - 如果字符串
A
为符合规范的超级括号序列,那么字符串(A)
、(SA)
、(AS)
均为符合规范的超级括号序列。 - 所有符合规范的超级括号序列均可通过上述 3 条规则得到。
例如,若
k
=
3
k = 3
k=3,则字符串 ((**()*(*))*)(***)
是符合规范的超级括号序列,但字符串 *()
、(*()*)
、((**))*)
、(****(*))
均不是。特别地,空字符串也不被视为符合规范的超级括号序列。
现在给出一个长度为
n
n
n 的超级括号序列,其中有一些位置的字符已经确定,另外一些位置的字符尚未确定(用 ?
表示)。小 w 希望能计算出:有多少种将所有尚未确定的字符一一确定的方法,使得得到的字符串是一个符合规范的超级括号序列?
可怜的小 c 并不会做这道题,于是只好请求你来帮忙。
输入格式
第一行,两个正整数 n , k n, k n,k。
第二行,一个长度为
n
n
n 且仅由 (
、)
、*
、?
构成的字符串
S
S
S。
输出格式
输出一个非负整数表示答案对 10 9 + 7 {10}^9 + 7 109+7 取模的结果。
样例 #1
样例输入 #1
7 3
(*??*??
样例输出 #1
5
样例 #2
样例输入 #2
10 2
???(*??(?)
样例输出 #2
19
提示
【样例解释 #1】
如下几种方案是符合规范的:
(**)*()
(**(*))
(*(**))
(*)**()
(*)(**)
【数据范围】
测试点编号 | n ≤ n \le n≤ | 特殊性质 |
---|---|---|
1 ∼ 3 1 \sim 3 1∼3 | 15 15 15 | 无 |
4 ∼ 8 4 \sim 8 4∼8 | 40 40 40 | 无 |
9 ∼ 13 9 \sim 13 9∼13 | 100 100 100 | 无 |
14 ∼ 15 14 \sim 15 14∼15 | 500 500 500 |
S
S
S 串中仅含有字符 ? |
16 ∼ 20 16 \sim 20 16∼20 | 500 500 500 | 无 |
对于 100 % 100 \% 100% 的数据, 1 ≤ k ≤ n ≤ 500 1 \le k \le n \le 500 1≤k≤n≤500。
算法思想
根据题目描述,符合规范的超级括号序列有下面几种情况:
()
(S)
,将S
加上一层括号是符合规范的。其中S
表示任意一个仅由不超过 k \bm{k} k 个字符*
组成的非空字符串;(A)
表示对一个符合规范的序列再加一层括号也符合规范的(SA)
、(AS)
表示在一个符合规范的序列左边(或右边)连接一个S
再加一层括号也是符合规范的AB
表示并排在一起的符合规范的序列也是合法的ASB
表示将S
放在并排在一起的符合规范的序列之中也是合法的
若
k
=
3
k = 3
k=3,则字符串 ((**()*(*))*)(***)
是否合法可以这样分析:
- 字符串的右边
(***)
是一个合法的序列(S)
,如果左边的子串((**()*(*))*)
是一个合法序列,则并排在一起符合规范AB
。 ((**()*(*))*)
如果其中的子串(**()*(*))
是一个合法序列,则符合规范(AS)
。(**()*(*))
如果其中的子串()*(*)
是一个合法序列,则符合规范(SA)
。()*(*)
符合规范ASB
,是一个合法序列
因此整个序列是符合规范的超级括号序列。
也就是说,对于字符串中任意一个区间 [ i , j ] [i,j] [i,j]判断是否符合规范,则需要判断其子区间是否符合规范,那么可以使用区间动态规划来处理。
状态表示
f [ i ] [ j ] f[i][j] f[i][j]表示将字符串中从 i i i到 j j j位置的字符确定后,得到的字符串是一个符合规范的超级括号序列的方案数。由于从 i i i到 j j j位置的字符可能是其中的一个子串,因此存在多种合法的状态:
-
f
[
i
]
[
j
]
[
0
]
f[i][j][0]
f[i][j][0]表示由单独的符合规范的超级括号序列组成的方案数,形式如
()
、(S)
、(A)
、(SA)
、(AS)
。 -
f
[
i
]
[
j
]
[
1
]
f[i][j][1]
f[i][j][1]表示由并排的符合规范的超级括号序列组成的方案数,形式如
AB
、ASB
。 -
f
[
i
]
[
j
]
[
2
]
f[i][j][2]
f[i][j][2]表示仅由不超过
k
\bm{k}
k 个字符
*
组成的方案数,形式如*
、**
…。需要注意的是只有*
组成的子串无法单独构成符合规范的超级括号序列。 -
f
[
i
]
[
j
]
[
3
]
f[i][j][3]
f[i][j][3]表示由符合规范的超级括号序列+不超过
k
\bm{k}
k 个
*
组成的方案数,形式如AS
。该方案也无法单独构成符合规范的超级括号序列。 -
f
[
i
]
[
j
]
[
4
]
f[i][j][4]
f[i][j][4]表示由不超过
k
\bm{k}
k 个
*
+ 符合规范的超级括号序列组成的方案数,形式如SA
。该方案也无法单独构成符合规范的超级括号序列。
最终的方案总数为 f [ 1 ] [ n ] [ 0 ] + f [ 1 ] [ n ] [ 1 ] f[1][n][0]+f[1][n][1] f[1][n][0]+f[1][n][1], 其中 n n n表示字符串 S S S的长度。
状态计算
-
f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0]表示由单独的符合规范的超级括号序列组成的方案数,那么去掉 i i i、 j j j位置的括号后,子串的形式可以是空串、
A
、AB
、S
、SA
、AS
的任意一种情况,因此, f [ i ] [ j ] [ 0 ] = f [ i + 1 ] [ j − 1 ] [ 0 ] + f [ i + 1 ] [ j − 1 ] [ 1 ] + f [ i + 1 ] [ j − 1 ] [ 2 ] + f [ i + 1 ] [ j − 1 ] [ 3 ] + f [ i + 1 ] [ j − 1 ] [ 4 ] f[i][j][0]=f[i+1][j-1][0]+f[i+1][j-1][1]+f[i+1][j-1][2]+f[i+1][j-1][3]+f[i+1][j-1][4] f[i][j][0]=f[i+1][j−1][0]+f[i+1][j−1][1]+f[i+1][j−1][2]+f[i+1][j−1][3]+f[i+1][j−1][4]注意:当长度为 2 2 2时,也就是只有括号的情况下,子串为空, f [ i ] [ j ] [ 0 ] = 1 f[i][j][0] = 1 f[i][j][0]=1。
-
f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1]表示由并排的符合规范的超级括号序列组成的方案数,如果以一个符合规范的超级括号序列
B
进行分类,那么子串的形式可以是A
、AS
。因此可以枚举最后一个B
和前面的分界点 k k k, f [ i ] [ j ] [ 1 ] = ∑ k = i j − 1 ( f [ i ] [ k ] [ 0 ] + f [ i ] [ k ] [ 1 ] + f [ i ] [ k ] [ 3 ] ) × f [ k + 1 ] [ j ] [ 0 ] f[i][j][1]=\sum_{k=i}^{j-1}(f[i][k][0]+f[i][k][1]+f[i][k][3])\times f[k+1][j][0] f[i][j][1]=∑k=ij−1(f[i][k][0]+f[i][k][1]+f[i][k][3])×f[k+1][j][0] -
f [ i ] [ j ] [ 2 ] f[i][j][2] f[i][j][2]表示仅由
*
组成的方案数,只能从状态 [ 2 ] [2] [2]转移过来,所以 f [ i ] [ j ] [ 2 ] = f [ i + 1 ] [ j ] [ 2 ] f[i][j][2]=f[i+1][j][2] f[i][j][2]=f[i+1][j][2]注意:当长度为 1 1 1时,且只有
*
的情况下, f [ i ] [ j ] [ 2 ] = 1 f[i][j][2] = 1 f[i][j][2]=1 -
f [ i ] [ j ] [ 3 ] f[i][j][3] f[i][j][3]表示由符合规范的超级括号序列+
*
组成的方案数,以后面的S
进行分类,前面的子串形式可以是A
,AB
,因此可以枚举S
和前面的分界点 k k k, f [ i ] [ j ] [ 3 ] = ∑ k = i j − 1 ( f [ i ] [ k ] [ 0 ] + f [ i ] [ k ] [ 1 ] ) × f [ k + 1 ] [ j ] [ 2 ] f[i][j][3]=\sum_{k=i}^{j-1}(f[i][k][0]+f[i][k][1]) \times f[k+1][j][2] f[i][j][3]=∑k=ij−1(f[i][k][0]+f[i][k][1])×f[k+1][j][2] -
f [ i ] [ j ] [ 4 ] f[i][j][4] f[i][j][4]表示由
*
+ 符合规范的超级括号序列组成的方案数,以前面的S
进行分类,前面的子串形式可以是A
,AB
,因此可以枚举S
和后面的分界点 k k k, f [ i ] [ j ] [ 4 ] = ∑ k = i j − 1 f [ i ] [ k ] [ 2 ] × ( f [ k + 1 ] [ j ] [ 0 ] + f [ k + 1 ] [ j ] [ 1 ] ) f[i][j][4]=\sum_{k=i}^{j-1} f[i][k][2] \times (f[k+1][j][0]+f[k+1][j][1]) f[i][j][4]=∑k=ij−1f[i][k][2]×(f[k+1][j][0]+f[k+1][j][1])
时间复杂度
- 状态数为 n × n × 5 n\times n\times5 n×n×5
- 状态计算需要枚举分界位置,时间复杂度最大为 O ( n ) O(n) O(n)
总的时间复杂度为 O ( 5 × n 3 ) = 625 , 000 , 000 O(5\times n^3)=625,000,000 O(5×n3)=625,000,000,由于枚举区间起点和分界位置的时间复杂度要低于 O ( n ) O(n) O(n),刚好可以AC。
代码实现
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 510, MOD = 1e9 + 7;
char s[N];
/*
f[i][j][0]表示由单独的符合规范的序列组成的方案数
f[i][j][1]表示由并排的符合规范的序列组成的方案数
f[i][j][2]表示仅由*组成的方案数
f[i][j][3]表示由符合规范的序列 + *组成的方案数
f[i][j][4]表示由* + 符合规范的序列组成的方案数
*/
int f[N][N][5];
//判断a和b是否匹配
bool is_match(char a, char b)
{
return a == b || a == '?';
}
int main()
{
int n, m;
cin >> n >> m;
cin >> s + 1; //字符串下标从1开始
//枚举区间长度
for(int len = 1; len <= n; len ++)
{
//枚举区间开始位置
for(int i = 1; i + len - 1 <= n; i ++)
{
int j = i + len - 1; //结束位置
if(len == 1) //区间长度为1
{
//只有1个星号
if(is_match(s[i], '*')) f[i][j][2] = 1;
}
else
{
//计算状态[0],由单独的符合规范的序列组成的方案数
if(is_match(s[i], '(') && is_match(s[j], ')'))
{
//当长度为2时,也就是只有括号的情况下,子串为空
if(len == 2) f[i][j][0] = 1;
//[0] = [0] + [1] + [2] + [3] + [4]
for(int k = 0; k < 5; k ++)
f[i][j][0] = (f[i][j][0] + f[i + 1][j - 1][k]) % MOD;
}
//计算状态[2],仅由*组成的方案数
if(len <= m && is_match(s[i], '*'))
f[i][j][2] = f[i + 1][j][2];
//枚举分界位置,计算其它状态[1]、[3]、[4]
for(int k = i; k < j; k ++)
{
f[i][j][1] = (f[i][j][1] + (f[i][k][0] + (LL)f[i][k][1] + f[i][k][3]) * f[k + 1][j][0]) % MOD;
f[i][j][3] = (f[i][j][3] + (f[i][k][0] + (LL)f[i][k][1]) * f[k + 1][j][2]) % MOD;
f[i][j][4] = (f[i][j][4] + f[i][k][2] * ((LL)f[k + 1][j][0] + f[k + 1][j][1])) % MOD;
}
}
}
}
cout << (f[1][n][0] + f[1][n][1]) % MOD;
return 0;
}