[CSP-S 2021] 括号序列
括号序列
题面
给定一个长度为 \(n\) ,包含 \(*\)、\(?\)、\((\)、\()\) 的字符串。
其中, \(?\) 处字符串缺失,可能是 \(*\)、\((\)、\()\) 中的任意一种。
定义超级括号序列如下:
-
\(()\) 和 \((S)\) 均为符合规范超级括号序列,其中 \(S\) 表示由长度不超过 \(k\) 的 \(*\) 组成。
-
如果 \(A\) 和 \(B\) 为超级括号序列,那么 \(AB\)、\(ASB\) 同样为符合规范的超级括号序列,其中 \(AB\) 表示把 \(A\) 和 \(B\) 拼在一起形成的字符串。
-
如果 \(A\) 为符合规范的超级括号序列,那么 \((A)\)、\((SA)\)、\((AS)\) 均为符合规范的超级括号序列。
-
所有符合规范的超级括号序列均可以由以上 \(3\) 条规则得到。
求给出的字符串一共能对应多少种符合规范的超级括号序列。
给出 \(n\)、\(k\) 和一个长度为 \(n\) 的字符串,其中 \(1\leq k\leq n\leq 500\) 。
分析
暴力
暴力分 \(15\) 分应该是直接搜索?\(O(3^n)\) ,期望能过前 \(3\) 个点。
区间 \(DP\)
之后发现好像其他暴力不怎么会,注意到 \(n\) 的范围只有 \(500\) ,我们需要一个 \(O(n^3)\) 的算法来解决这个问题。
考虑一个区间 \(DP\) 。
设 \(dp[l][r]\) 表示区间 \(l\) 到 \(r\) 的答案,之后,模拟上述的规则即可更新。
但之后你会发现你无法通过样例,思考这样 \(dp\) 的正确性,发现我们算重了。
对于 \(A\) ,我们也许会把它和后面一个匹配成功的 \(BC\) 合并,但是我们也会将 \(AB\) 合并,并在后面与 \(C\) 合并,考虑给我们的状态多加一维。
设 \(dp[l][r][0/1]\) 表示当前区间经历合并的方案数与没有经历合并的方案数,只让没有经历合并的方案和在它后面的经历过合并的方案合并,这样就能够避免这种情况。
一些细节
发现实现 \(ASB\) 是最后一个难关,很容易会让复杂度退化到 \(O(n^4)\) ,考虑设一个新的状态 \(f[l][r]\) 表示该区间内 \(SB\) 的方案数,之后可以与 \(AB\) 的情况一起转移。
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+10,MOD=1e9+7;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n,k;
int g[N][N],f[N][N],dp[N][N][2];
char arr[N];
inline bool check(int l,int r){
if(arr[l]=='('&&arr[r]==')') return true;
if(arr[l]=='('&&arr[r]=='?') return true;
if(arr[l]=='?'&&arr[r]==')') return true;
if(arr[l]=='?'&&arr[r]=='?') return true;
return false;
}
signed main()
{
// freopen("bracket.in","r",stdin);
// freopen("bracket.out","w",stdout);
n=read(),k=read();
cin>>arr+1;
for(int l=1;l<=n;l++){
if(arr[l]!='('&&arr[l]!='?') continue;
int cnt=0;
for(int r=l+1;r<=n;r++){
if(arr[r]==')'||arr[r]=='?') dp[l][r][0]=1;
if(arr[r]!='*'&&arr[r]!='?') break;
cnt++; if(cnt>k) break;
}
}
for(register int len=2;len<=n;len++){ //枚举区间长度
for(register int l=1;l<=n-len+1;l++){ //枚举左端点
int r=l+len-1; //右端点
int cnt=0;
for(register int x=l;x<r;x++){
if(arr[x]!='*'&&arr[x]!='?') break;
cnt++; if(cnt>k) break;
f[l][r]=(f[l][r]+(dp[x+1][r][0]+dp[x+1][r][1])%MOD)%MOD;
}
cnt=0;
for(register int x=r;x>l;x--){
if(arr[x]!='*'&&arr[x]!='?') break;
cnt++; if(cnt>k) break;
g[l][r]=(g[l][r]+(dp[l][x-1][0]+dp[l][x-1][1])%MOD)%MOD;
}
for(register int x=l;x<r;x++)
dp[l][r][1]=(dp[l][r][1]+dp[l][x][0]*((f[x+1][r]+(dp[x+1][r][0]+dp[x+1][r][1])%MOD)%MOD)%MOD)%MOD;
if(check(l,r)){ //端点可以为()
dp[l][r][0]=((dp[l][r][0]+dp[l+1][r-1][0])%MOD+dp[l+1][r-1][1])%MOD;
dp[l][r][0]=(dp[l][r][0]+f[l+1][r-1])%MOD;
dp[l][r][0]=(dp[l][r][0]+g[l+1][r-1])%MOD;
}
}
}
printf("%lld\n",(dp[1][n][0]+dp[1][n][1])%MOD);
return 0;
}