[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;
}
posted @ 2021-10-25 23:47  _Famiglistimo  阅读(371)  评论(2编辑  收藏  举报