题目链接:https://www.lanqiao.cn/courses/2786/learning/?id=280825
题目解析:
这是一道动态规划题
合法的括号序列满足以下两个条件
①从左往右数,任意一个位置的左括号数量均不少于右括号
②从右往左数,任意一个位置的右括号数量均不少于左括号
②也可表表述为:将整个括号序列翻转,并将左括号视作右括号,右括号视作左括号时,①仍然成立
下面来谈谈①的实现:
考虑到n个左括号的全排列只有一种,因此我们约定,仅在右括号的前面添加左括号
显然最多添加左括号的数量为整个序列长度
dp[i][j]表示在第i个位置上(算上第i个位置),左括号比右括号多j个的方案数
则状态转移方程为
dp[i][j]=dp[i-1][0]+dp[i-1][1]+...+dp[i-1][j]+dp[i-1][j+1]
当j >= 1时,注意到:
dp[i][j-1]=dp[i-1][0]+dp[i-1][j]+...+dp[i-1][j]
综合上述两式,即可得到:
dp[i][j]=dp[i][j-1]+dp[i-1][j+1](j>=1)
dp[i][0]=dp[i-1][0]+dp[i-1][1](j==0)
后在最后一个位置看dp[i][j]>0&&j取最小值,即最后一个位置上左括号比右括号多的数量取最小值的方案数,即满足①
翻转后再进行一次①,得到另外一种方案
两种方案相乘(取模),即为结果。
代码:
//#define local
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<string.h>
#define MAXN 5005
const int mod=1e9+7;
typedef long long ll;
using namespace std;
char str[MAXN];
ll dp[MAXN][MAXN]={0};
int main(){
#ifdef local
freopen("data.in","rb",stdin);
//freopen("data.out","wb",stdout);
#endif
scanf("%s",str+1);
int len=strlen(str+1);
dp[0][0]=1;
for(int i=1;i<=len;++i){
for(int j=0;j<=len;++j){
if(str[i]=='('&&j!=0)
dp[i][j]=dp[i-1][j-1];
else if(str[i]==')'){
if(j==0)
dp[i][j]=(dp[i-1][0]+dp[i-1][1])%mod;
else
dp[i][j]=(dp[i][j-1]+dp[i-1][j+1])%mod;
}
}
}
ll left=0;
for(int j=0;j<=len;++j){
if(dp[len][j]>0){
left=(dp[len][j])%mod;
break;
}
}
reverse(str+1,str+1+len);
fill(dp[0],dp[0]+MAXN*MAXN,0);
dp[0][0]=1;
for(int i=1;i<=len;++i){
for(int j=0;j<=len;++j){
if(str[i]==')'&&j!=0)
dp[i][j]=dp[i-1][j-1];
else if(str[i]=='('){
if(j==0)
dp[i][j]=(dp[i-1][0]+dp[i-1][1])%mod;
else
dp[i][j]=(dp[i][j-1]+dp[i-1][j+1])%mod;
}
}
}
ll right=0;
for(int j=0;j<=len;++j){
if(dp[len][j]>0){
right=(dp[len][j])%mod;
break;
}
}
printf("%lld",left*right%mod);
return 0;
}