\(\rm P7914\) [CSP 2021] 括号序列 加深理解

做题简记。

这里觉得第一篇题解的做法是最优秀的,因为这才是真正的dp强调的不重不漏

这个做法只设了一个dp数组,应该跟其他设两个之类的解法是共通的。

解法

观察

结合数据范围,我们确定这是一道区间dp题。先设出 \(f[l, r]\) 的基本状态,然后观察如何转移

仔细观察一个复杂的合法序列是怎么被一步一步拼接起来的。具体的,有:

  • S \(\to\) (S)
  • S + A \(\to\) (SA)
  • A + S \(\to\) (AS)
  • A + B \(\to\) AB
  • A + S + B \(\to\) ASB

那么略加思考我们会发现这题其实很容易算重。那么为了解决这个问题,我们就不能无脑合并了,而是要变换一下传统的转移方式。考虑将 ***(单个*串,记为 X)和 (...)(左右括号匹配的合法串,记为 Y,例如((*)*()),但不包含 ()**()*())作为一个长括号串的“零件”,一步步从左往右拼接。

那么在将 X 和 Y 拼接的过程中就涉及到很多转移的中间状态,具体的有:

  • A (...)*(...)*(最右端是*
  • B *(...)*(...)(最左端是*
  • C (...)*(...)(合法串)
  • D *(...)*(...)*(左右都是*

那么在草稿纸上写写画画,我们不难得到这四种中间状态(以及 Y)的转化关系:

(原串 + 零件 = 新串)

直接拼接 \(6\) 种:

  • A + Y = C
  • B + X = D
  • B + Y = B
  • C + X = A
  • C + Y = C
  • D + Y = B

套括号 \(3\) 种:

  • (A) = Y
  • (B) = Y
  • (C) = Y

观察到 ***(...) 在参与拼接的时候都是作为极长的零件,而且默认是从左往右拼接,那么就不存在算重(比如说 ()()() = () + ()() = ()() + () )的问题了。

设计状态

有了以上的分析,我们可以写出最终状态及转移方程(参照了原题解):

  • \(f[l, r, 0]\) 表示 \(s[l, r]\)*** 的拼法个数。
  • \(f[l, r, 1]\) 表示 \(s[l, r]\)(...) 的拼法个数。
  • \(f[l, r, 2]\) 表示 \(s[l, r]\)A 的拼法个数。
  • \(f[l, r, 3]\) 表示 \(s[l, r]\)C 的拼法个数。(包含了(...) 的情况,相当于“广义”)
  • \(f[l, r, 4]\) 表示 \(s[l, r]\)B 的拼法个数。
  • \(f[l, r, 5]\) 表示 \(s[l, r]\)D 的拼法个数。(包含了*** 的情况,相当于“广义”)

转移方程

转移如下:

  • X: \(f[l, r, 0]\) 直接特判。

  • Y: \(f[l, r, 1] = \text{valid}(l, r)\times (f[l, r, 0] + f[l, r, 2] + f[l, r, 3] + f[l, r, 4])\) ,即 (A) = Y, (B) = Y, (C) = Y

\(\rm valid\) 表示 \(s[l]\)\(s[r]\) 能不能配成 ()

  • A: \(f[l, r, 2] = \sum_i f[l, i, 3]\times f[i + 1, r, 0]\) ,即 C + X = A

  • C: \(f[l, r, 3] = f[l, r, 1] + \sum_i (f[l, i, 2] + f[l, i, 3])\times f[i + 1, r, 1]\),即 A + Y = C, C + Y = C

  • B: \(f[l, r, 4] = \sum_i (f[l, i, 4] + f[l, i, 5])\times f[i + 1, r, 1]\),即 B + Y = B, D + Y = B

  • D: \(f[l, r, 5] = f[l, r, 0] + \sum_i f[l, i, 4]\times f[i + 1, r, 0]\),即 B + X = D

一些细节:

如何理解“广义”?就是说将这些特殊的串放到上面一般的转移中也是合法的,故也要算进答案。

为了方便 \(f[l, r, 0]\) 的特判,我们令 \(f[i, i – 1, 0] = 1\),具体见代码。

最终答案为 \(f[1, n, 3]\)(C 串)。

Code

用记忆化搜索实现的代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 505, mod = 1e9 + 7;
using LL = long long;

char s[N];
int n, k, vis[N][N];
LL f[N][N][6];

inline auto &dp(int l, int r, int i) { return f[l][r][i]; }
inline int valid(int l, int r) { return (s[l] == '(' || s[l] == '?') && (s[r] == ')' || s[r] == '?'); }

void dfs(int l, int r) {
    if(l > r) return;
    if(vis[l][r]) return;
    for(int i = l; i < r; ++i) dfs(l, i), dfs(i + 1, r);
    if(r - l + 1 <= k) dp(l, r, 0) = dp(l, r - 1, 0) && (s[r] == '*' || s[r] == '?');
    vis[l][r] = 1;
    if(r - l < 1) goto ED;
    if(valid(l, r)) 
        (dp(l, r, 1) += dp(l + 1, r - 1, 0) + dp(l + 1, r - 1, 2) + 
                        dp(l + 1, r - 1, 3) + dp(l + 1, r - 1, 4)) %= mod;
    for(int i = l; i < r; ++i) {
        (dp(l, r, 2) += dp(l, i, 3) * dp(i + 1, r, 0)) %= mod;
        (dp(l, r, 3) += (dp(l, i, 2) + dp(l, i, 3)) * dp(i + 1, r, 1)) %= mod;
        (dp(l, r, 4) += (dp(l, i, 4) + dp(l, i, 5)) * dp(i + 1, r, 1)) %= mod;
        (dp(l, r, 5) += dp(l, i, 4) * dp(i + 1, r, 0)) %= mod;
    }
    ED:
    (dp(l, r, 5) += dp(l, r, 0)) %= mod;
    (dp(l, r, 3) += dp(l, r, 1)) %= mod;
}

signed main() {
    scanf("%d %d\n%s", &n, &k, s + 1);
    for(int i = 1; i <= n; ++i) dp(i, i - 1, 0) = 1;
    dfs(1, n);
    printf("%lld", dp(1, n, 3));
    return 0;
}

原文地址:http://www.cnblogs.com/Doge297778/p/16810817.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性