「CSP-S 2021 括号序列」题解

考场挂分器,送分题变成送命题。


一、状态:

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 [ i , j ] [i, j] [i,j] 这个区间内组成超级括号序列的方案数

f [ i ] [ j ] f[i][j] f[i][j] 表示在 d p [ i ] [ j ] dp[i][j] dp[i][j] 的要求上,添加限制:方案中,括号 i i i, j j j 匹配。

a n s [ i ] [ j ] ans[i][j] ans[i][j] 表示 [ i , j ] [i, j] [i,j] 这个区间能不能做到 [ l , r ] [l, r] [l,r] 全为 “*”。

c h e c k ( l , r ) = [ [ [ s [ l ] = ? ] ∨ [ s [ l ] = ∗ ] ] ∧ [ [ s [ r ] = ? ] ∨ [ s [ r ] = ∗ ] ] ] check (l, r) = \bigg[ \Big[ [s[l] = ?] \lor [s[l] = *] \Big] \wedge \Big[ [s[r] = ?] \lor [s[r] = *] \Big] \bigg] check(l,r)=[[[s[l]=?][s[l]=]][[s[r]=?][s[r]=]]]

attention: 这里区间的含义和数学上是一样的,不会是空集。


二、初始化:

a n s ans ans 即判断这个区间内是不是全是 “?” 或 “*”。 d p , f dp, f dp,f 全部设为 0 0 0


三、状态转移:

考虑所有情况,每种情况对应一种转移。


  1. ()

这种情况特判即可。

d p [ l ] [ r ] = f [ l ] [ r ] = c h e c k ( l , r ) ∗ [ l + 1 = = r ] dp[l][r] = f[l][r] = check (l, r) * [l + 1 == r] dp[l][r]=f[l][r]=check(l,r)[l+1==r]


  1. (S)

这是 s [ l ] , s [ r ] s[l], s[r] s[l],s[r] 已经匹配好了,则 [ l + 1 , r − 1 ] [l + 1, r - 1] [l+1,r1] 全为 * 就行了。

d p [ l ] [ r ] = f [ l ] [ r ] = a n s [ l + 1 ] [ r − 1 ] dp[l][r] = f[l][r] = ans[l + 1][r - 1] dp[l][r]=f[l][r]=ans[l+1][r1]


  1. (SA)

我们枚举 S 的最后一个字符的位置。

即统计 [ l + 1 , i ] [l + 1, i] [l+1,i] 全为 *, [ i + 1 , r − 1 ] [i + 1, r - 1] [i+1,r1] 为超级括号序列的方案数。

d p [ l ] [ r ] = f [ l ] [ r ] = ∑ i = l + 1 i + 1 ≤ r − 1 a n s [ l + 1 ] [ i ] ∗ d p [ i + 1 ] [ r − 1 ] dp[l][r] = f[l][r] = \sum_{i = l + 1}^{i + 1 \leq r - 1} ans[l + 1][i] * dp[i + 1][r - 1] dp[l][r]=f[l][r]=i=l+1i+1r1ans[l+1][i]dp[i+1][r1]


  1. (AS)

同理 3。

d p [ l ] [ r ] = f [ l ] [ r ] = ∑ i = l + 1 i + 1 ≤ r − 1 d p [ l + 1 ] [ i ] ∗ a n s [ i + 1 ] [ r − 1 ] dp[l][r] = f[l][r] = \sum_{i = l + 1}^{i + 1 \leq r - 1} dp[l + 1][i] * ans[i + 1][r - 1] dp[l][r]=f[l][r]=i=l+1i+1r1dp[l+1][i]ans[i+1][r1]


  1. AB

我们枚举 A 的最后一个字符的位置。

同理 3,我们可以写出这样的转移

d p [ l ] [ r ] = ∑ i = l i ≤ r − 1 d p [ l ] [ i ] ∗ d p [ i + 1 ] [ r ] dp[l][r] = \sum_{i = l}^{i \leq r - 1} dp[l][i] * dp[i + 1][r] dp[l][r]=i=lir1dp[l][i]dp[i+1][r]

但是发现它会算重:

  e . g . e.g. e.g.

    6 10
    ()()()

在这种情况下,因为有 [1, 2] 和 [3, 6], [1,4] 和 [5, 6] 的两种分裂方法,所以这个序列被重复计算了。为了防止一种满足要求的答案因可以被多个地方裂开而算重,我们可以钦定 A 是一个第一个和最后一个元素匹配的超级括号序列。这样的话,从不同的点分裂,那对应的答案序列一定不一样。(比如两个序列分别从 i, j 分裂,序列 1 s[l] 对应的是 s[i], 而序列 2 s[l] 对应的是 s[j],所以说这两个序列一定不一样)。

所以方程应该写为

d p [ l ] [ r ] = ∑ i = l i ≤ r − 1 f [ l ] [ i ] ∗ d p [ i + 1 ] [ r ] dp[l][r] = \sum_{i = l}^{i \leq r - 1} f[l][i] * dp[i + 1][r] dp[l][r]=i=lir1f[l][i]dp[i+1][r]


  1. ASB

同理 5,枚举左端点和右端点匹配的超级括号 A 的最后一位,以及 S 的最后一位。

即统计 A : [ l , i ] , S : [ i + 1 , j − 1 ] , B [ j , r ] A:[l, i], S:[i + 1, j - 1], B[j, r] A:[l,i],S:[i+1,j1],B[j,r] 的方案数。

d p [ l ] [ r ] = ∑ i = l i ≤ r ∑ j = i + 2 j ≤ r − 1 f [ l ] [ i ] ∗ a n s [ i + 1 ] [ j − 1 ] ∗ d p [ j ] [ r ] dp[l][r] = \sum_{i = l}^{i\leq r} \sum_{j = i + 2}^{j \leq r - 1}f[l][i] * ans[i + 1][j - 1] * dp[j][r] dp[l][r]=i=lirj=i+2jr1f[l][i]ans[i+1][j1]dp[j][r]

发现是一个 O ( n 2 ) O (n ^ 2) O(n2) 的转移,总时间复杂度会变为 O ( n 4 ) O(n^4) O(n4),考虑优化。

a n s [ i + 1 ] [ j − 1 ] ans[i + 1][j - 1] ans[i+1][j1] 是一个关于 j j j 的单调不递增函数,而只有当 a n s [ i + 1 ] [ j − 1 ] = 1 ans[i + 1][j - 1] = 1 ans[i+1][j1]=1 时才可能有贡献,所以不妨考虑找到这个临界点,记作 p,则 ∀ k ∈ [ i + 2 , p ] , a n s [ i + 1 ] [ k − 1 ] = 1 \forall k \in [i + 2,p], ans[i + 1][k - 1] = 1 k[i+2,p],ans[i+1][k1]=1

将转移式中的公因数提出来。

d p [ l ] [ r ] = ∑ i = l i ≤ r ( f [ l ] [ i ] ∗ ∑ j = i + 2 j ≤ p d p [ j ] [ r ] ) dp[l][r] = \sum_{i = l}^{i\leq r} (f[l][i] * \sum_{j = i + 2}^{j \leq p}dp[j][r]) dp[l][r]=i=lir(f[l][i]j=i+2jpdp[j][r])

发现后面的求和里只与 j j j 有关,所以可以用一维后缀和进行优化。

f a l l [ i ] = ∑ j = i j ≤ r d p [ j ] [ r ] fall[i] = \sum_{j=i}^{j \leq r} dp[j][r] fall[i]=j=ijrdp[j][r]

则式子可以化为

d p [ l ] [ r ] = ∑ i = l i ≤ r ( f [ l ] [ i ] ∗ ( f a l l [ i + 2 ] − f a l l [ p + 1 ] ) ) dp[l][r] = \sum_{i = l}^{i\leq r} (f[l][i] * (fall[i + 2] - fall[p + 1])) dp[l][r]=i=lir(f[l][i](fall[i+2]fall[p+1]))


小 Trick: 发现只考虑操作 1 ~ 3 时, d p [ l ] [ r ] = f [ l ] [ r ] dp[l][r] = f[l][r] dp[l][r]=f[l][r], 所以我们可以在计算了操作 1 ~ 3 对 d p [ l ] [ r ] dp[l][r] dp[l][r] 的贡献之后,将它直接赋给 f [ l ] [ r ] f[l][r] f[l][r]


参考代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define ULL unsigned long long
#define PII pair <int, int>
#define MP(x,y) make_pair (x, y)
#define rep(i,j,k) for (int i = (j); i <= (k); i++)
#define per(i,j,k) for (int i = (j); i >= (k); i--)
#define fi first
#define se second

template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T>
void read (T &x) {
	x = 0; T f = 1;
	char ch = getchar ();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar ();
	}
	while (ch >= '0' && ch <= '9') {
		x = (x << 3) + (x << 1) + ch - '0';
		ch = getchar ();
	}
	x *= f;
}
template <typename T>
void write (T x) {
	if (x < 0) {
		x = -x;
		putchar ('-');
	}
	if (x < 10) {
		putchar (x + '0');
		return;
	}
	write (x / 10);
	putchar (x % 10 + '0');
}
template <typename T>
void print (T x, char ch) {
	write (x); putchar (ch);
}

const int Maxn = 500;
const LL Mod = 1e9 + 7;

int n, k;
char s[Maxn + 5];
LL fall[Maxn + 5];
LL dp[Maxn + 5][Maxn + 5], f[Maxn + 5][Maxn + 5];
LL ans[Maxn + 5][Maxn + 5];

bool check (int l, int r) {
	if (s[l] == '(' && s[r] == ')') return 1;
	if (s[l] == '(' && s[r] == '?') return 1;
	if (s[l] == '?' && s[r] == ')') return 1;
	if (s[l] == '?' && s[r] == '?') return 1;
	return 0;
}
void add (LL &x, LL y) {
	x = (x + y % Mod) % Mod;
}

int main () {
	freopen ("C:\\Users\\Administrator\\Desktop\\vscode\\1.in", "r", stdin);
	freopen ("C:\\Users\\Administrator\\Desktop\\vscode\\1.out", "w", stdout);
	read (n); read (k);
	scanf ("%s", s + 1);
	
	for (int l = 1; l <= n; l++)
		for (int r = l; r <= n; r++) {
			if (s[r] != '*' && s[r] != '?') break;
			if (r - l + 1 > k) break;
			ans[l][r] = 1;
		}
	for (int len = 2; len <= n; len++) {
		for (int l = 1; l + len - 1 <= n; l++) {
			int r = l + len - 1;
			
            if (check (l, r)) {
				if (l + 1 == r) add (dp[l][r], 1); // 1
				else add (dp[l][r], dp[l + 1][r - 1] + ans[l + 1][r - 1]); // 2
                for (int i = l + 1; i + 1 <= r - 1; i++) {
					add (dp[l][r], ans[l + 1][i] * dp[i + 1][r - 1]); // 3
					add (dp[l][r], dp[l + 1][i] * ans[i + 1][r - 1]);//4
				}
            }
			f[l][r] = dp[l][r];

			for (int i = l; i + 1 <= r; i++)
				add (dp[l][r], f[l][i] * dp[i + 1][r]);//5
			
			memset (fall, 0, sizeof fall);
			for (int i = r; i >= l; i--) {
				add (fall[i], fall[i + 1] + dp[i][r]);
			}
			int p = 0;
			for (int i = l; i <= r; i++) {
				// /*   O (n ^ 3)
				p = Max (p, i + 1);
				while (ans[i + 1][(p + 1) - 1]) {
					p++;
				}
				add (dp[l][r], f[l][i] * (fall[i + 2] - fall[p + 1]));//6
				// */
				/*      O (n ^ 4)
				for (int j = i + 2; j <= r - 1; j++) {
					//[l, i][i + 1, j - 1][j, r]
					add (dp[l][r], f[l][i] * ans[i + 1][j - 1] * dp[j][r]);
				}
				*/
			}
		}
	}
	write (dp[1][n]);
	return 0;
}
posted @ 2021-10-27 13:29  C2022lihan  阅读(28)  评论(0编辑  收藏  举报