CSP-S 2021 T2 括號序列 題解
阿新 • • 發佈:2021-10-24
考場上想了114514年不知道怎麼避免算重。
看到 $ n \leq 500 $ 就想到區間DP。設 $ f_{l,r,0/1/2/3} $ 表示方案數,0表示A,1表示SA,2表示AS,3表示SAS。並且設 $ s_{l,r} =\sum_{i=0}^2 f_{l,r,i} $ 表示當 $ [l,r] $ 被括號括起來後是A的方案數。我們考慮怎麼樣統計答案來避免算重。
首先,如果 $ str_l = \text{(} ,str_r = \text{)} $ ,那麼 $ f_{l,r,0} = s_{l+1,r-1} $ 。對於 $ f_{l,r,0/1} $ ,可以在右邊最後一個(...)處統計答案;對於 $ f_{l,r,2} $ ,可以在左邊最後一個(...)處統計答案;對於 $ f_{l,r,3} $ ,考慮在左邊的S處統計答案。
想到怎麼去重後就可以很好地DP了。
具體轉移和一些邊界情況見程式碼。
#include <bits/stdc++.h> using namespace std; inline int read(){ int f=1,r=0;char c=getchar(); while(!isdigit(c))f^=c=='-',c=getchar(); while(isdigit(c))r=(r<<1)+(r<<3)+(c&15),c=getchar(); return f?r:-r; } const int N=507,mod=1e9+7; inline void add(int &x,int y){x+=y;if(x>=mod)x-=mod;} int n,K,f[N][N][4],s[N][N]; char str[N]; bool ok[N][N]; inline bool ck(int i,char c){return str[i]==c || str[i]=='?';} inline bool brac(int i,int j){return ck(i,'(') && ck(j,')');} int main(){ n=read(),K=read(),scanf("%s",str+1); for(int i=1;i<=n;i++){ ok[i][i-1]=1; for(int j=i;j<=min(i+K-1,n);j++) ok[i][j]=ok[i][j-1]&ck(j,'*'); } for(int i=1;i<n;i++)f[i][i+1][0]=brac(i,i+1),s[i][i+1]=ok[i][i+1]+brac(i,i+1); for(int i=1;i<=n;i++)s[i][i-1]=1,s[i][i]=ok[i][i]; for(int len=3;len<=n;len++) for(int l=1,r=len;r<=n;l++,r++){ if(brac(l,r))f[l][r][0]=s[l+1][r-1]; for(int k=l;k<r;k++){ if(brac(k+1,r)){ add(f[l][r][0],1ll*(f[l][k][0]+f[l][k][2])*s[k+2][r-1]%mod); add(f[l][r][1],1ll*(f[l][k][1]+f[l][k][3]+ok[l][k])*s[k+2][r-1]%mod); } if(brac(l,k))add(f[l][r][2],1ll*s[l+1][k-1]*(f[k+1][r][2]+f[k+1][r][3]+ok[k+1][r])%mod); if(ok[l][k])add(f[l][r][3],f[k+1][r][2]); } s[l][r]=ok[l][r]; for(int o=0;o<3;o++)add(s[l][r],f[l][r][o]); } printf("%d\n",f[1][n][0]); return 0; }