[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;
}
posted @ 2021-10-24 20:59  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(380)  评论(4编辑  收藏  举报