这个题去年暑假就见过了…
一直没看懂题解 感到难以理解…
本来这次是想直接放弃掉…
最终硬着头皮想通了…
感谢固执又天真的自己hhh
题目:
几个需要清楚的问题:
①关于括号序列合法性:对于一段括号序列,从左往右起,一个字符一个字符的往前看,对于每一段小的括号序列 --‘(’-- 数量 大于等于 --‘)’--
数量,那么整个括号序列就合法。
②关于 --‘(’-- 和 --’)’--
的添加可以分开来讨论:括号是被添加到原序列的括号与括号之间的空隙里的,假如左括号和右括号加入的是不同的空隙,那么它们必然是互不影响的。如果加入的是同一个空隙,那么右括号的添加必然在左括号之前,否则括号配对,添加无意义,不存在顺序的影响,那么也是互不影响的,所以我们将其的添加分开讨论。
③上面第二点清楚了之后,如何利用其解决问题:可以分开讨论之后,我们可以判断在原括号序列添加左括号使之变得合法,然后变换原括号序列(先将括号序列逆序,再将左括号变成右括号,右括号变成左括号),这样调整之后我们在变换后的括号序列中添加左括号使之变得合法(相当于在原括号序列添加右括号)。
下面是举例解释:
通常我们只需要添加一种括号就能使整个括号序列合法。
例如
原括号序列:((() 左括号数量大于等于右括号数量,合法,不用添加
变换后序列:())) 左括号数量小于右括号数量,不合法,需要添加
但也有特殊情况,需要两种括号都加
原括号序列:) ) ( ( 乍一看好像相等,理解了第一点就知道此序列不合法,需要添加。
变换后序列:) ) ( ( 和原括号序列一样,不合法,需要添加。
这样一来我们的问题变成了在括号序列中添加左括号使合法的问题,所以每碰到一个右括号,我们就可以在其前边添加左括号,从刚好合法(左右括号相等)到更多的左括号(上限是括号序列长度)。
④dp数组的含义:之前看题解都写的一样,但是那样想我感觉始终有些问题想不明白。
自己想了一种含义(其实和原含义差不多,但是更好理解了):
dp[i][j]是指前i个括号字符之前 添加不知道多少个(可以是0个) --’(’-- 使这前i个括号字符合法(合法的含义又是 --’(’-- 比
--’)’-- 多或者相等,所以j从0开始) 的种数。
可能有点拗口…去掉括号注释来看就是:前i个括号字符之前添加不知道多少个左括号使前i个括号字符合法的种数。
也就是说加多少个我们是不管的,我们只考虑添加后的结果中左括号比右括号多多少个。而j是下标必定大于等于0,于是如果dp[i][j]这个状态是可以存在的那么这个值一定不是0,也就是不可能是0种。
⑤递推公式:
遇到 --‘(’-- :我们只考虑在 --‘)’-- 前添加 --‘(’--
使这个右括号之前的括号序列合法。遇到左括号的时候,证明在这个左括号之前的括号序列已经被判断过如何使其合法了,那么加上这个左括号依然合法,所以我们不需要管这个左括号。也就是在他前面的括号序列添加左括号使其合法的种数等于加上这个左括号之后这个序列需要添加的左括号种数。
dp[i][j]=dp[i-1][j-1];
遇到 --‘)’-- :如果这个加上这个右括号的序列本身合法,那么我们仅需添加0个左括号就能使其合法,如果不合法就需要添加刚好使得其合法的左括号甚至可以更多。
dp[i][j] = dp[i-1][0] + dp[i-1][1] + … + dp[i-1][j] + dp[i-1][j+1]
解释:
要得到前i个字符的序列左括号比右括号多j个的合法情况
可以由
前i-1个字符的序列左括号比右括号多0个(刚好合法)的情况,再在这个右括号前面加j+1个左括号得到
前i-1个字符的序列左括号比右括号多1个的情况,再在这个右括号前面加j个左括号得到
.
.
.
前i-1个字符的序列左括号比右括号多j+1个的情况,再在这个右括号前面加0个左括号得到
(其实就是左括号数在这些右括号之间分配的种数)
可上面那样做一定会超时
然后注意到
dp[i][j-1] = dp[i-1][0] + dp[i-1][1] + … dp[i-1][j]
∴dp[i][j] = dp[i-1][j+1] + dp[i][j-1]
和多重背包类似用到了当前行的状态
初始化:dp[0][0]=1 这是显而易见的
另外的细节在注释里:
#include <iostream> #include <cstring> #include <algorithm> #include <stdio.h>
using namespace std; typedef long long LL; const int MOD=1000000007; const int N
=5010; LL dp[N][N]; char str[N]; int len; LL func() { memset(dp,0,sizeof(dp));
dp[0][0]=1; for(int i=1;i<=len;i++)//一个括号一个括号的判断 { if(str[i]=='(') { for(int j=1
;j<=len;j++) { dp[i][j]=dp[i-1][j-1];//不用考虑dp[i][0] 因为dp[i-1][-1]是不合法的情况 不存在 为0
} } else { dp[i][0]=(dp[i-1][0]+dp[i-1][1])%MOD;//特判防止越界 这里数据短,用的是优化前的推断 for(int
j=1;j<=len;j++) { dp[i][j]=(dp[i-1][j+1] + dp[i][j-1])%MOD; } } } for(int i=0;i
<=len;i++) if(dp[len][i]) return dp[len][i];
//我们需要的就是长度为len添加括号的合法情况,而从前往后遍历出现的第一个有可能的情况就是需要括号数最少的情况,因为左括号可以加很多个,我们仅需添加最少的情况
return -1; } int main() { scanf("%s",str+1);//从下标为1开始 len=strlen(str+1); LL l=
func(); reverse(str+1,str+len+1); for(int i=1;i<=len;i++) { if(str[i]=='(') str[
i]=')'; else str[i]='('; } LL r=func(); cout<<l*r%MOD; return 0; }