CSP-S2021 T2 括号序列

CSP-S 2021 T2

考场上没推出来,我太蒻了

分析

  这个做法肯定不是我原创的 我太蒻了,这是我看了 luogu 上的一篇题解之后加上我的理解写出来的分析。原文在这里:传送门 luogu P7914 CSP-S 2021 括号匹配 题解

  首先,肯定是区间 DP,不用想了。那我们设 F [ l , r F[_{l, r} F[l,r 表示从 l l l r r r 的合法序列数量。但是这道题有个很烦的事情就是,有很多种状态它都是合法的,所以我们要对每一种转移做讨论。所以我们对于几种不同的转移把 F F F 扩展成 3 维,它们分别代表一下的含义(S 表示全是 * 的字符串,A 表示任意合法字符串):

  1. F l , r , 0 F_{l, r, 0} Fl,r,0:表示这样:S,全是 ’ * ’ 的序列
  2. F l , r , 1 F_{l, r, 1} Fl,r,1:表示这样:(S) 的序列,两头都是括号且这两头的括号匹配的序列
  3. F l , r , 2 F_{l, r, 2} Fl,r,2:表示这样:(A)S(A)…(A)S 的序列,左边以括号开头,有边以 * 结尾的序列
  4. F l , r , 3 F_{l, r, 3} Fl,r,3:表示这样:"(A)S(A)S…(A)" 的序列,左边以括号开头,有边以括号结尾的序列
  5. F l , r , 4 F_{l, r, 4} Fl,r,4:表示这样:“S(A)S…(A)” 的序列,左边以 * 开头,有边以括号结尾的序列
  6. F l , r , 5 F_{l, r, 5} Fl,r,5:表示这样:“S(A)S…S” 的序列,左边以 * 开头,有边以 * 结尾的序列

  然后就是转移,我们定义一个 bool 类型的函数 canMatch(i, j),返回第 i i i 位和第 j j j 为能否匹配成一对括号。然后下面是转移方程:

F l , r , 0 直 接 特 判 就 好 了 . . . F l , r , 1 = ( F l + 1 , r − 1 , 0 + F l + 1 , r − 1 , 2 + F l + 1 , r − 1 , 3 + F l + 1 , r − 1 , 4 ) [ c a n M a t c h ( l , r ) ] F l , r , 2 = ∑ i = l r − 1 F l , i , 3 F l + 1 , r , 0 F l , r , 3 = F l , r , 1 + ∑ i = l r − 1 ( F l , i , 2 + F l , i , 3 ) F i + 1 , r , 1 F l , r , 4 = ∑ i = l r − 1 ( F l , i , 4 + F l , i , 5 ) F i + 1 , r , 1 F l , r , 5 = F l , r , 0 + ∑ i = l r − 1 F l , i , 4 F i + 1 , r , 0 \begin{aligned} & F_{l, r, 0} \quad 直接特判就好了... \\ & F_{l, r, 1} = (F_{l+1, r-1, 0} + F_{l+1, r-1, 2} + F_{l+1, r-1, 3} + F_{l+1, r-1, 4})[canMatch(l, r)] \\ & F_{l, r, 2} = \sum_{i=l}^{r-1} F_{l, i, 3} F_{l+1, r, 0} \\ & F_{l, r, 3} = F_{l, r, 1} + \sum_{i=l}^{r-1} (F_{l, i, 2} + F_{l, i, 3})F_{i+1, r, 1} \\ & F_{l, r, 4} = \sum_{i=l}^{r-1}(F_{l, i, 4} + F_{l, i, 5})F_{i+1, r, 1} \\ & F_{l, r, 5} = F_{l, r, 0} + \sum_{i=l}^{r-1}F_{l, i, 4}F_{i+1, r, 0} \end{aligned} Fl,r,0...Fl,r,1=(Fl+1,r1,0+Fl+1,r1,2+Fl+1,r13+Fl+1,r1,4)[canMatch(l,r)]Fl,r,2=i=lr1Fl,i,3Fl+1,r,0Fl,r,3=Fl,r,1+i=lr1(Fl,i,2+Fl,i,3)Fi+1,r,1Fl,r,4=i=lr1(Fl,i,4+Fl,i,5)Fi+1,r,1Fl,r,5=Fl,r,0+i=lr1Fl,i,4Fi+1,r,0

  最后,答案就是 F 1 , n , 3 F_{1, n, 3} F1,n,3

  现在我们来解释一下上面的动态转移方程是怎么来的。我们以 F l , r , 3 = F l , r , 1 + ∑ i = l r − 1 ( F l , i , 2 + F l , i , 3 ) F i + 1 , r , 1 F_{l, r, 3} = F_{l, r, 1} + \sum_{i=l}^{r-1} (F_{l, i, 2} + F_{l, i, 3})F_{i+1, r, 1} Fl,r,3=Fl,r,1+i=lr1(Fl,i,2+Fl,i,3)Fi+1,r,1 为例来解释一下,剩下的读者可以自行理解(主要是懒得打那么多 qwq)。

  首先我们看看这个式子:
F l , r , 3 = F l , r , 1 + ∑ i = l r − 1 ( F l , i , 2 + F l , i , 3 ) F i + 1 , r , 1 F_{l, r, 3} = F_{l, r, 1} + \sum_{i=l}^{r-1} (F_{l, i, 2} + F_{l, i, 3})F_{i+1, r, 1} Fl,r,3=Fl,r,1+i=lr1(Fl,i,2+Fl,i,3)Fi+1,r,1

长得多好看啊qwq,首先我们要知道这里的每一项代表什么意思,首先我们知道 F l , r , 3 F_{l, r, 3} Fl,r,3 表示的是 [ l , r ] [l, r] [l,r] 是一个左边以括号开头,有边以括号结尾的序列的情况总数。所以它可能有两种情况:

  1. 第一种:两头的括号是匹配的,也就是 F l , r , 1 F_{l, r, 1} Fl,r,1
  2. 第二种:两头的括号不是匹配的,所以他就是以前半部分为任意一个以括号开头的合法串,后半部分是任意一个以括号开头且一个以括号结尾且这两头的括号是匹配的的合法串。

  好,知道了每一项是什么,那我们再来看看这个式子里面的 加号乘号 分别代表什么。首先这个式子能被分成两大部分: F l , r , 1 F_{l, r, 1} Fl,r,1 ∑ i = l r − 1 ( F l , i , 2 + F l , i , 3 ) F i + 1 , r , 1 \sum\limits_{i=l}^{r-1} (F_{l, i, 2} + F_{l, i, 3})F_{i+1, r, 1} i=lr1(Fl,i,2+Fl,i,3)Fi+1,r,1。再照着我们上面的分析看一看,是不是 F l , r , 1 F_{l, r, 1} Fl,r,1 就代表了我们上面所说的第一种情况,那一大坨求和就代表了我们所说的第二种情况。把这两种情况的所有可能情况的数量加起来就是我们要的这种情况的的可能性的数量(这句话有点绕,多读几遍能看懂的,其实就是加法原理 qwq)。

F l , r , 1 F_{l, r, 1} Fl,r,1 都好懂,我们现在就来看看后面一大坨的求和 F l , r , 1 F_{l, r, 1} Fl,r,1 ∑ i = l r − 1 ( F l , i , 2 + F l , i , 3 ) F i + 1 , r , 1 \sum\limits_{i=l}^{r-1} (F_{l, i, 2} + F_{l, i, 3})F_{i+1, r, 1} i=lr1(Fl,i,2+Fl,i,3)Fi+1,r,1,究竟是啥意思。首先是这个求和号,非常好理解嘛,不就是枚举区间 [ l , r ) [l, r) [l,r) 里面的一个中间的点吗。然后呢?然后就能把这个区间拆成两部分了。这两部分分别由什么组成呢?分别由 半部分:任意一个以括号开头的合法串后半部分:任意一个以括号开头且一个以括号结尾且这两头的括号是匹配的的合法串 组成。那么显然 F l + 1 , r , 1 F_{l+1, r, 1} Fl+1,r,1 就代表了后半部分。那么前半部分就是 ( F l , i , 2 + F l , i , 3 ) (F_{l, i, 2} + F_{l, i, 3}) (Fl,i,2+Fl,i,3) (任意一个以括号开头的合法串就分为两种情况,一种是状态 2,另一种是 3 嘛)。那么为什么是乘法呢?乘法原理嘛,如果做一件事情要分成两步来完成,第一步有 a a a 个选择,第二步有 b b b 个选择,那么完成这个事情就有 a × b a \times b a×b 个选择了。

  好了,我们来总结一下。式子中的加法就是根据加法原理,把完成这种状态的构造分成了两种情况,把每种情况的可能数加起来就是构造这个状态的可能数。式子中的乘法就是根据乘法原理,完成这个构造需要前半部分是一种状态,后半部分接上另一种状态,那么就把这两种状态的可能数乘起来就是我们要的可能数了。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define MOD (int)(1e9 + 7)
#define in read()

inline int read(){
	int x = 0; char c = getchar();
	while(c < '0' or c > '9') c = getchar();
	while('0' <= c and c <= '9'){
		x = x * 10 + c - '0'; c = getchar();
	}
	return x;
}

int n = 0; int k = 0;
int f[510][510][6] = { 0 };
char s[510];

bool canMatch(int a, int b){
	return (s[a] == '(' or s[a] == '?') and (s[b] == ')' or s[b] == '?');
}

void dp(){
	for(int i = 1; i <= n; i++) f[i][i-1][0] = 1;
	for(int len = 1; len <= n; len++){
	    for(int l = 1; l <= n - len + 1; l++){
	        int r = l + len - 1;
	        if(len <= k) f[l][r][0] = f[l][r-1][0] and (s[r] == '*' or s[r] == '?');
	        if(len >= 2){
               if(canMatch(l, r)) f[l][r][1] = (f[l+1][r-1][0] + f[l+1][r-1][2] + f[l+1][r-1][3] + f[l+1][r-1][4]) % MOD;
	            for(int i = l; i <= r - 1; i++){
	                f[l][r][2] = (f[l][r][2] + f[l][i][3] * f[i+1][r][0]) % MOD;
	                f[l][r][3] = (f[l][r][3] + (f[l][i][2] + f[l][i][3]) * f[i+1][r][1]) % MOD;
	                f[l][r][4] = (f[l][r][4] + (f[l][i][4] + f[l][i][5]) * f[i+1][r][1]) % MOD;
	                f[l][r][5] = (f[l][r][5] + f[l][i][4] * f[i+1][r][0]) % MOD;
	            }
	        }
	        f[l][r][5] = (f[l][r][5] + f[l][r][0]) % MOD;
	        f[l][r][3] = (f[l][r][3] + f[l][r][1]) % MOD;
	    }
	}
}

signed main(){
    n = in; k = in;
    scanf("%s", s + 1);
    dp();
    printf("%lld\n", f[1][n][3]);
}

  刚好 50 行 qwq。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值