这一篇绝不是千篇一律的题解,希望你能认真看完,希望对你有所帮助。
几个需要清楚的问题:
①关于括号序列合法性:对于一段括号序列,从左往右起,一个字符一个字符的往前看,对于每一段小的括号序列 –‘(’– 数量 大于等于 –‘)’– 数量,那么整个括号序列就合法。
②关于 –‘(’– 和 –’)’– 的添加可以分开来讨论:括号是被添加到原序列的括号与括号之间的空隙里的,假如左括号和右括号加入的是不同的空隙,那么它们必然是互不影响的。如果加入的是同一个空隙,那么右括号的添加必然在左括号之前,否则括号配对,添加无意义,不存在顺序的影响,那么也是互不影响的,所以我们将其的添加分开讨论。
③上面第二点清楚了之后,如何利用其解决问题:可以分开讨论之后,我们可以判断在原括号序列添加左括号使之变得合法,然后变换原括号序列(先将括号序列逆序,再将左括号变成右括号,右括号变成左括号),这样调整之后我们在变换后的括号序列中添加左括号使之变得合法(相当于在原括号序列添加右括号)。
下面是举例解释:
通常我们只需要添加一种括号就能使整个括号序列合法。
例如
原括号序列:((() 左括号数量大于等于右括号数量,合法,不用添加
变换后序列:())) 左括号数量小于右括号数量,不合法,需要添加
但也有特殊情况,需要两种括号都加
原括号序列:) ) ( ( 乍一看好像相等,理解了第一点就知道此序列不合法,需要添加。
变换后序列:) ) ( ( 和原括号序列一样,不合法,需要添加。
这样一来我们的问题变成了在括号序列中添加左括号使合法的问题,所以每碰到一个右括号,我们就可以在其前边添加左括号,从刚好合法(左右括号相等)到更多的左括号(上限是括号序列长度)。
④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 这是显而易见的
另外的细节在注释里:
C++ 代码
#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;
}
附上本人csdn原文链接:https://blog.csdn.net/qq_45302640/article/details/122726632
由于本人很忙并且这个题解的逻辑已基本完备,不再做后续解答,不懂就多看几遍,一定是可以理解的。写下这篇题解的初衷是为了想让大家少走弯路,弄懂这个问题前,我一度对这道题产生了抵触的心理。一遍遍尝试才最终理清了其中的关系,愿我们的努力都不会被辜负,愿共勉。
给大佬跪了,太谢谢了。这题要看自闭了。
谢谢大佬,蓝桥杯蒟蒻也想ak。。。
呜呜呜看了两个晚上,终于懂了,谢谢大佬
为什么代码提交到蓝桥杯上是运行错误wc
括号序列合法性判断漏掉了一个条件:整个序列左右括号数量相等。这个条件和任意前缀左括号数量 ≥ 右括号 数量(任意后缀 右括号数量 >= 左括号数量)两者缺一不可,应当强调,需要同时考虑这两种情况。总序列左右括号数量相等 && 前缀条件 < = > 前缀条件 && 后缀条件 。正是考虑到了数量相等 加上 前缀条件,才有 方案数 = F[ n ] [ i ] * F [ n ] [ j ] F[ n ] [ i ] 中 n 为括号数量 , i 为 最少的 满足考虑前 n 个括号 使前缀条件成立的 ,左括号比右括号多出的数量 ,j 为反过来 使后缀条件成立的 右括号比左括号多的数量。 添加左右括号使合法的方案互不干扰,故相乘
这里没太看懂,您能再通俗一点的解释下为什么分开枚举可以使最后左括号的数量等于右括号的数量嘛
第一次枚举得到 加入最少的左括号使得任意前缀 左括号数量 >= 右括号数量 那么前缀为整个字符串的时候也满足 左括号数量 >= 右括号数量。第二次枚举可以得到加入最少的右括号使得任意后缀 右括号数量 >= 左括号数量,对于整个字符串来看 有左括号 >= 右括号 右括号>= 左括号 推出右括号数量=左括号
收到,谢谢您
太棒了,给你点赞
dp[i][0]=(dp[i-1][0]+dp[i-1][1])%MOD;请问这里为啥是dp[i - 1][0] 既然dp[i - 1][-1]不合法那直接舍去不就行了吗?所以就是dp[i][0] = dp[i - 1][1] % MOD,我这个理解错在那里?
兄弟 你这完全错了啊 你说的完全是两种不同的情况 这两个我写的位置都不一样… 再仔细看看吧
嗯嗯,我刚刚想通了
请问一下如果分开计算的话可不可能出现位于不同的空隙添加的左右括号发生匹配的情况呢
分开计算的前提就是已经判断了互相不会有影响
我想问的就是这个前提
为什么是互不影响的,我觉得可能会出现位于不同的空隙添加的左右括号发生匹配的情况,可以帮我解释一下吗qaq
在添加左括号的时候,眼里只有不能使序列合法多出来的原来序列的右括号(新加进去的右括号如果是在前面被加入一定已经是在它之前的序列合法了),同样反过来也是是一样,我们不是随便加括号的,并且考虑的也是使之合法需要添加最少的括号数。而且前提限定左括号比右括号多合法,这样也很好的约束了我们一定不会使添加的左右括号会配对的情况,比如只用添加一种括号来使之合法,那么另一种括号就不会再被添加。我只能解释到这个地步,你再理解理解吧。
谢谢你的解释
不太理解为什么多添加了左括号也合法
其实就是可能后边还会有右括号出现和左括号配对,把所有可能的情况都枚举出来,但是最终取的结果还是刚好合法,也就是相等的。
感谢感谢, 还有一个小问题,就是为什么初始化的时候只把 dp[0][0] 初始化为了 1 而不是把dp[0][ 1~n]都初始化为一。
我的理解是d[0][0]是在考虑前 0 个符号情况下 左括号 比 右括号 多 0 个的情况为 1 ,
那不管多几个,都是随便放,为什么其他的情况不为 1 呢
不是噢,0个符号的时候不考虑添加括号的问题,dp[0][0]是本身什么都没有时的情况,dp[0][i] (i!=0) 则属于不可能存在的情况,所以为0才是正确的,我们在进行判断是否添加括号时是从i等于1开始进行判断的。
好的好的,感谢
还是不太理解为什么多添了左括号也合法
如果最后返回的是刚好合法的结果,那一定是f[n][0]呀,表示将整个字符串都变合法,并且左括号的数量恰好等于有括号的数量,为什么还要从0到len枚举呢?
这个我举了特殊情况的例子哦,建议返回去看看,大多数情况是只用加一种括号合法,但是有必须加两种括号的情况,那么这样就不是刚好相等。
因为在添加右括号的时候,右括号数量也是大于等于左括号数量的—满足右括号<=左括号(合法)的最少方案
在添加左括号的时候,左括号数量大于等于右括号数量—满足左括号>=右括号(合法)的最少方案
左右都添加后的最终结果是 左括号数量恰好等于右括号数量