1. 程式人生 > 其它 >CSP-S 2021 T2 括號序列 題解

CSP-S 2021 T2 括號序列 題解

考場上想了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;
}