[SMOJ1989]圆括号

xiaoxiao2021-02-28  92

这一题很明显是 DP,但是状态的表示就有方法了。主要是要在解决括号序列的合法匹配问题上做功夫。

首先明确这样的一条基本事实:在一个合法的括号序列中,对于任意的 i ,序列前 i 位中的左括号个数总是大于或等于右括号个数。

我在比赛时候用了非常繁琐的状态,因为我的思路非常简单粗暴,记 f[i][j][k][0/1] 为: X 串取了前 i 位, Y 串取了前 j 位,当前有 k 个左括号待配对,当前为左括号(0)/右括号(1)的方案数。

也就是说我实际上是用了到目前为止左括号个数减去右括号个数保证状态的合法性,则最终的答案应该为 f[|X|][|Y|][0][1]

边界条件:f[0][0][0][1] = 1;

转移就分类讨论一下,当前状态可能是取 X 串的第 i 位得来的,也可能是取 Y 串的第 j 位。两种情况其实是类似的,只不过决策时要考察的值的前两维分别为 f[i1][j] f[i][j1] 而已,因此这里只以前者为例,后者同理。

因为取的是 Xi ,还要再根据其值分类讨论一下:

如果是左括号,那么新增了一个待配对的左括号,之前就应该是 f[i1][j][k1] [0或1都可以]。显然这里必须满足 k>0 ,才能保证之前 k10 ,否则不合法。

而如果是右括号,则为之前某个待配对的左括号配了对,之前就应该是 f[i1][j][k+1] [0或1都可以]。显然这里必须满足 k<i+j ,道理很容易想。

这样,总的时间复杂度为 O(n3) 的,对于本题来说已经足够了。

我在这里需要反思一点,即 k 的取值上限可能达到两个字符串长度之和,而我在定义的时候只开了一个字符串长度,导致 WA 了半天。

参考代码:

#include <algorithm> #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream> using namespace std; const int MAXL = 50 10; const long long MOD = 1000000007LL; char X[MAXL], Y[MAXL]; int Xlen, Ylen; long long f[MAXL][MAXL][MAXL << 1][5]; bool isok() { if (X[1] != '(' && Y[1] != '(' || X[Xlen] != ')' && Y[Ylen] != ')') return false; int cnt[256] = {0}; for (int i = 1; i <= Xlen; i ) cnt[X[i]]; for (int i = 1; i <= Ylen; i ) cnt[Y[i]]; return cnt['('] == cnt[')']; } int main(void) { freopen("1989.in", "r", stdin); freopen("1989.out", "w", stdout); int T; scanf("%d", &T); while (T--) { memset(f, 0, sizeof f); scanf("%s%s", X 1, Y 1); Xlen = strlen(X 1); Ylen = strlen(Y 1); if (((Xlen Ylen) & 1) || !isok()) { puts("0"); continue; } f[0][0][0][1] = 1LL; for (int i = 0; i <= Xlen; i ) { int ic = X[i] - '('; for (int j = 0; j <= Ylen; j ) { int jc = Y[j] - '('; for (int k = 1; k <= i j; k ) { if (i > 0 && ic == 0) f[i][j][k][0] = f[i - 1][j][k - 1][0] f[i - 1][j][k - 1][1]; f[i][j][k][0] %= MOD; if (j > 0 && jc == 0) f[i][j][k][0] = f[i][j - 1][k - 1][0] f[i][j - 1][k - 1][1]; f[i][j][k][0] %= MOD; } for (int k = 0; k < i j; k ) { if (i > 0 && ic == 1) f[i][j][k][1] = f[i - 1][j][k 1][0] f[i - 1][j][k 1][1]; f[i][j][k][1] %= MOD; if (j > 0 && jc == 1) f[i][j][k][1] = f[i][j - 1][k 1][0] f[i][j - 1][k 1][1]; f[i][j][k][1] %= MOD; } } } printf("%lld\n", f[Xlen][Ylen][0][1]); } return 0; }

话说回来,其实完全可以进一步优化,赛后讲评的时候 Monad 上去讲了 n2 的做法。

首先要发现,最后一维可以省去,考虑当前字符是左括号还是右括号其实是一样的,可以统一处理,在决策的过程中不需要去考虑它(仔细体会一下这句话)。

当然,这样只是在状态上少了 0/1,对于时间来说没什么大变化。既然可以 n2 做,而前两个参数又必不可少(否则无法满足取的顺序限制),说明——

第三维也是可以省去的。

其实上面状态中为了保证合法性而添加的一维表明我的潜意识已经有点接近正解了,但是我只看到了表面。

这里其实可以用 f[i][j] 表示 X 串取了前 i 位, Y 串取了前 j 位,且保证满足到目前为止左括号个数大于等于右括号个数的方案数。

可以通过维护一个前缀和之类的东西,快速算出 X 串前 i 位和 Y 串前 j 位中左、右括号的个数,之后分为两种情况讨论:

如果合法(左括号个数大于等于右括号个数),那么 f[i][j] += f[i-1][j]+f[i][j-1];否则 f[i][j] = 0。

最后的答案为 f[|X|][|Y|] 。总的时间复杂度为 O(n2) ,完全有能力应对更大规模的题目。

最后要注意,可以预先特判一些不合法的情况,如 X 串和 Y 串的第一位都为右括号或最后一位都为左括号,以及两个串中总的左括号个数不等于右括号个数。

Ghastlcon 大佬的代码(%%%%%):

#include <iostream> #include <algorithm> #include <cstdio> #include <cstring> #include <string> #define N 70 #define MOD 1000000007 using namespace std; long long f[N][N]; int Count(string &x, int p, char c) { int i, j; for(i = j = 0;i <= p;i ++) j += x[i] == c; return j; } int main() { int t; string x, y; int i, j; freopen("1989.in" , "r", stdin ); freopen("1989.out", "w", stdout); cin >> t; while(t --) { cin >> x >> y; memset(f, 0, sizeof(f)); for(i = 0, f[0][0] = 1;i <= (signed)x.size();i ++) for(j = 0;j <= (signed)y.size();j ++) if(Count(x, i - 1, ')') + Count(y, j - 1, ')') <= Count(x, i - 1, '(') + Count(y, j - 1, '(')) f[i][j] = (f[i][j] + ((i ? f[i - 1][j] : 0) + (j ? f[i][j - 1] : 0)) % MOD) % MOD; cout << f[x.size()][y.size()] * (Count(x, i - 1, ')') + Count(y, j - 1, ')') == Count(x, i - 1, '(') + Count(y, j - 1, '(')) << endl; } return 0; }

转载请注明原文地址: https://www.6miu.com/read-46918.html

最新回复(0)