括号序列
代码如下
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxS = 5005;
string s;
int n;
ll f[maxS][maxS];
ll cal()
{
memset(f,0,sizeof f);
f[0][0] = 1;
for(int i=0;i<s.size();i++)
{
if(s[i]=='(')
{
for(int j=1;j<=n;j++)
f[i+1][j] = f[i][j-1];
}else
{
f[i+1][0] = (f[i][0] + f[i][1])%mod;
for(int j=1;j<=n;j++)
f[i+1][j] = (f[i+1][j-1] + f[i][j+1])%mod;
}
}
for(int j=0;j<=n;j++)
if(f[n][j]) return f[n][j];
}
int main()
{
cin >> s;
n = s.size();
ll l = cal();
reverse(s.begin(),s.end());
for(int i=0;i<s.size();i++)
if(s[i]=='(') s[i] = ')';
else s[i] = '(';
ll r = cal();
ll ans = l*r%mod;
cout << ans << endl;
return 0;
}
分析(来自y总):
关键词:理解+dp
1.序列合法,①'('数量=')'数量②那么任意一个前缀中'('的数量大于')'
——>(与题无关) cnt计数,'('+1 '')'-1,一旦cnt<0则一定要添左括号
2.本质不同 and 加左括号和有括号独立
e.g _(_(_(_)_ _是可插入的地方
关于括号的独立:
假设先添左括号_( 【(】 ( 【(】 (_)_,再添右括号
①右括号与添加的(在不同位置显然独立
②在同一位置如_( 【)(】 ( 【(】 (_)_只能是)(。因为若是()显然就本质相同了,因为这么一填对原序列无影响,换个意思说填不填()都没关系。
3.插入左括号和插入右括号本质相同,将序列翻转后,每个字符也翻转即可。((()填右括号相当于()))填左括号。
所以总方案数=左*右 = 左*右翻转
所以只考虑填左括号方案数就行
4.关于dp[i][j]
i表示第i个字符,j表示左括号比右括号多j,值表示方案数。(这些方案总是合法的,插入多少个(不管,只看结果)
答案为dp[n][0]-dp[n][n]中第一个非0的,因为此刻最小合法
(个人理解:一个序列不知道要填多少个(,但范围显然是0-n。()))添加到最后为了合法j显然为0,而((()为2,所以j也是0-n,而越靠近0添加也就越少,方案数应该越少)
就把)当成隔板(①【)】②【)】③【)】④就形成4个区域,在里面插入左括号顺序无关。最后总的方案数相当于4个区域(的数量,比如此时是1 0 0 0
所以转态转移方程
(:dp[i][j] = dp[i-1][j-1] 因为前面到这里刚好+1个(,而且(前面不能加'('(为了保障唯一性)
):dp[i][j] = dp[i-1][0] + dp[i-1][1] + ... + dp[i-1][j+1] 因为这里划分假设前面加了j+1-0个括号,又因为本身是),所以j前面对应0-j+1
又dp[i-1][0] + dp[i-1][1] + ... + dp[i-1][j] = dp[i][j-1]
所以dp[i][j]= dp[i][j-1] + dp[i-1][j+1];
注意dp[i][0] = dp[i-1][0] + dp[i-1][1];
dp[0][0] = 1
e.g()))填左括号,i是行,j是列
0 1 2 3 4
0 1 0 0 0 0
1 0 1 0 0 0
2 1 1 1 1 1
3 2 3 4 5 5
4 5 9 14 19 19
感觉自己写得乱七八糟的,将就看下吧,下面几个连接感觉讲得比我清楚,交叉补充看吧:
AcWing 3420. 括号序列(蓝桥杯C++ AB组辅导课) - AcWing
AcWing 3420. 括号序列(如果一直没能理解这道题,就进来看看这个吧) - AcWing
2021蓝桥杯省赛J:括号序列-思维+动态规划_括号序列题解b3758-CSDN博客
没做出来原因:
我做出来就有鬼了