题目大概说给一个长m的括号序列s,要在其前面和后面添加括号使其变为合法的长度n的括号序列,p+s+q,问有几种方式。(合法的括号序列当且仅当左括号总数等于右括号总数且任何一个前缀左括号数大于等于右括号数)
我这么想的:n-m<=2000,因而可以dp计算p和q的方案数,同时在各个地方加入s进行转移。
- dp[0/1][i][j]表示s没有/有加入时,p和q前i个括号已经确定且还有j的左括号还没匹配的方案数
- 注意到任何前缀的左括号都是大于等于右括号的,因此j这一维不会小于0。
- 那么转移,我用我为人人转移,就是:
- 尾巴加上左括号:
d[0][i+1][j+1]+=d[0][i][j]
- 尾巴加上右括号:
d[0][i+1][j-1]+=d[0][i][j]
- 尾巴加上左括号和s:
d[1][i+1][j+1+cnt]+=d[0][i][j](cnt=s中左括号数-右括号数)
- 尾巴加上右括号和s:
d[1][i+1][j-1+cnt]+=d[0][i][j](cnt=s中左括号数-右括号数)
- 从已经加上s的转移:
d[1][i+1][j+1]+=d[1][i][j]
d[1][i+1][j-1]+=d[1][i][j]
这些转移前提是要合法。合法情况还有一点要注意的是,s不一定都能随便放到p和q任何一个地方的,因为可能出现p+s的序列不合法,即p+s序列中存在前缀左括号数小于右括号数,所以还要用j这一维的值与cnt的值比较。
看了下题解,它的做法是求出dp[i][j],这个dp[i][j]既是前缀方案数又是后缀方案数,因为后缀相当于前缀反过来,其右括号数目大于等于左括号数目。通过枚举p的i和j来确定q,而q是后缀,而二者的方案数乘积为答案的一部分贡献。
另外这一题写完后直接提交差点点1A了,不过感觉还不错,难得考虑全面。。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 int d[2][2222][4444]; 6 int main(){ 7 char ch; 8 int n,m,cnt=0,precnt=0; 9 scanf("%d%d",&n,&m); 10 int N=n-m; 11 for(int i=0; i<m; ++i){ 12 scanf(" %c",&ch); 13 if(ch=='(') ++cnt; 14 else --cnt; 15 precnt=min(precnt,cnt); 16 } 17 d[0][0][0]=1; 18 if(0<=cnt&& cnt<=2*N && precnt==0) d[1][0][cnt]=1; 19 for(int i=0; i<N; ++i){ 20 for(int j=0; j<=2*N; ++j){ 21 if(j+1<=2*N){ 22 d[0][i+1][j+1]+=d[0][i][j]; 23 d[0][i+1][j+1]%=1000000007; 24 } 25 if(j-1>=0){ 26 d[0][i+1][j-1]+=d[0][i][j]; 27 d[0][i+1][j-1]%=1000000007; 28 } 29 if(j+1<=2*N){ 30 d[1][i+1][j+1]+=d[1][i][j]; 31 d[1][i+1][j+1]%=1000000007; 32 } 33 if(j-1>=0){ 34 d[1][i+1][j-1]+=d[1][i][j]; 35 d[1][i+1][j-1]%=1000000007; 36 } 37 if(0<=j+1+cnt && j+1+cnt<=2*N && j+1+precnt>=0){ 38 d[1][i+1][j+1+cnt]+=d[0][i][j]; 39 d[1][i+1][j+1+cnt]%=1000000007; 40 } 41 if(0<=j-1+cnt && j-1+cnt<=2*N && j-1+precnt>=0){ 42 d[1][i+1][j-1+cnt]+=d[0][i][j]; 43 d[1][i+1][j-1+cnt]%=1000000007; 44 } 45 } 46 } 47 printf("%d",d[1][N][0]); 48 return 0; 49 }