[CSP-S 2021] 括号序列 题解
Statement
[P7914 CSP-S 2021] 括号序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Solution
考虑区间 DP ,设 \(dp[i][j]\) 表示区间 \([i,j]\) 合法方案数
按题意转移,发现难点在于 ASB
考虑怎么处理 \(ASB\) 的合并,发现暴力枚举会变成 \(O(n^4)\)
不妨设 \(f[i][j]\) 表示区间 \(AS\) 的方案数,\(g[i][j]\) 表示区间 \(SB\) 的方案数,有:
\[f[i][j]=\sum dp[i][k]\times fg[k+1][j]\\
g[i][j]=\sum fg[i][k]\times dp[k+1][j]
\]
其中,\(fg[i][j]\) 表示这个区间是否可以全部填 \(*\) ,可以 \(O(n^2)\) 预处理出来
那么,对于 \(ASB\)这种,我们就可以类比 \(AB\) 的转移了
\[dp[i][j]+=\sum dp[i][k]\times g[k+1][j]
\]
注意到可能会算重,所以 \(f,g\) 用一个去贡献就可以了
像这样做,发现过不了样例,我们仍然算重了
怎么说?对于 \(A\) ,我们也许会把它和后面一个匹配成功的 \(BC\) 合并,但是我们也会将 \(AB\) 合并,并在后面与 \(C\) 合并
意思说,我们只让没有经历合并的方案和在它后面的经历过合并的方案合并
这个时候,\(dp\) 状态未免显得有点 \(naive\) ,考虑多设一维
\(dp[i][j][1/0]\) 表示区间 是/否 经历过合并操作 的合法方案数
于是我们可以写出我们最后的状态转移方程了:
\[\begin{align*}
&dp[i][j][1]+=\sum dp[i][k][0]\times (dp[k+1][j][0]+dp[k+1][j][1])\\
&dp[i][j][1]+=\sum dp[i][k][0]\times g[k+1][j]\\
&dp[i][j][0]+=dp[i+1][j-1][0]+dp[i+1][j-1][1]+f[i+1][j-1]+g[i+1][j-1]
\end{align*}
\]
当然,对于 \(fg\) 数组,我们可以发现它的作用类似 \(dp\) 数组的初始化
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e2+5;
const int mod = 1e9+7;
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;
}
char s[N];
int dp[N][N][2],f[N][N],g[N][N];
int n,K;
bool pd(int l,int r){
if(s[l]=='('&&s[r]==')')return true;
if(s[l]=='('&&s[r]=='?')return true;
if(s[l]=='?'&&s[r]==')')return true;
if(s[l]=='?'&&s[r]=='?')return true;
return false;
}
signed main(){
n=read(),K=read(),scanf("%s",s+1);
for(int l=1;l<=n;++l){
int cnt=0;
if(s[l]!='('&&s[l]!='?')continue;
for(int r=l+1;r<=n;++r){
if(s[r]==')'||s[r]=='?')dp[l][r][0]=1;//细节 break 顺序
if(s[r]!='*'&&s[r]!='?')break;
if(++cnt>K)break;
}
}
for(int len=2;len<=n;++len)
for(int l=1;l+len-1<=n;++l){
int r=l+len-1,cnt=0;
for(int x=l;x<r;++x){
if(s[x]!='*'&&s[x]!='?')break;
if(++cnt>K)break;
(g[l][r]+=(dp[x+1][r][0]+dp[x+1][r][1])%mod)%=mod;
}
cnt=0;
for(int x=r;x>l;--x){
if(s[x]!='*'&&s[x]!='?')break;
if(++cnt>K)break;
(f[l][r]+=(dp[l][x-1][0]+dp[l][x-1][1])%mod)%=mod;
}
for(int x=l;x<r;++x)
(dp[l][r][1]+=dp[l][x][0]*(g[x+1][r]+(dp[x+1][r][0]+dp[x+1][r][1])%mod)%mod)%=mod;
if(pd(l,r))
(dp[l][r][0]+=(dp[l+1][r-1][0]+dp[l+1][r-1][1])%mod+(f[l+1][r-1]+g[l+1][r-1])%mod)%=mod;
}
printf("%lld\n",(dp[1][n][0]+dp[1][n][1])%mod);
return 0;
}