https://ac.nowcoder.com/acm/contest/33187/K
题意
给定一个长度为
n
n
n 的括号序列
a
a
a,其是长度为
m
m
m 的合法括号序列
b
b
b 的一个子序列。
问,括号序列
b
b
b 一共有多少种?
1 ≤ T ≤ 100 , 1 ≤ n ≤ m ≤ 200 , ∑ m ≤ 1 0 3 1≤T≤100,\ 1≤n≤m≤200,\ \sum m \leq10^3 1≤T≤100, 1≤n≤m≤200, ∑m≤103
思路
如果不考虑长序列合不合法,只是问满足短序列为其子序列的长序列一共多少个,那么可以:
定义状态 f[i, j]
表示,短序列前 j
项是长序列前 i
项的子序列,满足的长序列的方案数。
状态转移,遍历长序列每个位置 i
,遍历短序列每个位置 j
:
- 当短序列第 j 个位置为
(
时,如果长序列第i
个位置为(
时,方案数 += f[i-1, j-1];如果第i
个位置为)
时,方案数 += f[i-1, j]。 - 当短序列第 j 个位置为
)
时,如果长序列第i
个位置为)
时,方案数 += f[i-1, j-1];如果第i
个位置为(
时,方案数 += f[i-1, j]。
而现在除了要满足短序列为长序列的子序列之外,还要满足长序列时合法括号序列,所以需要多加一维,用于判断最终是否合法。
定义状态 f[i, j, k]
表示,长序列的前 i
位,和短序列的前 j
位匹配 并且 左括号比右括号多 k
个的方案数。
那么最终的方案数就为 f[m, n, 0]
。
状态转移:
第一维枚举长序列的每一位 i
,第二维枚举短序列的每一位 j
,第三维枚举长序列中左括号比右括号多的个数 k
。
- 如果 j=0,如果长序列
i
位置为(
,f[i,j,k] += f[i-1, j, k-1]
;如果为)
时,f[i,j,k] += f[i-1, j, k+1]
; - 否则如果短序列 a[j] 为
(
时,如果长序列i
位置为(
,f[i,j,k] += f[i-1, j-1, k-1]
;如果为)
时,f[i,j,k] += f[i-1, j, k+1]
; - 否则短序列 a[j] 为
)
,如果长序列i
位置为)
,f[i,j,k] += f[i-1, j-1, k+1]
;如果为(
时,f[i,j,k] += f[i-1, j, k-1]
;
注意数组越界。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define int long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
const int N = 210, mod = 1e9+7;
int T, n, m;
char a[N];
int f[N][N][N];
void add(int &x, int y){
x = (x + y) % mod;
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n >> m;
cin >> a + 1;
for(int i=0;i<=m;i++)
for(int j=0;j<=min(n, i);j++)
for(int k=0;k<=i;k++)
f[i][j][k] = 0;
f[0][0][0] = 1;
for(int i=1;i<=m;i++)
{
for(int j=0;j<=min(n, i);j++)
{
for(int k=0;k<=i;k++)
{
if(j == 0)
{
if(k >= 1) add(f[i][j][k], f[i-1][j][k-1]);
add(f[i][j][k], f[i-1][j][k+1]);
}
else if(a[j] == '(')
{
if(k >= 1) add(f[i][j][k], f[i-1][j-1][k-1]); // '('
add(f[i][j][k], f[i-1][j][k+1]); // ')'
}
else
{
add(f[i][j][k], f[i-1][j-1][k+1]); // ')'
if(k >= 1) add(f[i][j][k], f[i-1][j][k-1]);
}
}
}
}
cout << f[m][n][0] << endl;
}
return 0;
}