Loading

[2003 Ptz WC Day6] Little Brackets

思路

题意

给定序列长度 2n2n , 求「深度」为 kk 的合法括号序列有多少个
其中「深度」定义为序列前缀中左括号数量与右括号数量之差的最大值

性质

对于一个正则括号串, 一定可以表示成唯一的形式: (X)Y(X)Y
其中 X,YX, Y 都是正则括号串

正则括号串的性质:
正则括号串可以通过递归方式定义:

  • 空串是正则括号串。
  • 如果 SS 是正则括号串,则 (S)(S) 也是正则括号串。
  • 如果 SSTT 是正则括号串,则 STST((SSTT的连接)) 也是正则括号串
证明

证明

假设我们有一个正则括号串 SS,它可以表示为两种形式:
S=(X1)Y1S = (X_1)Y_1
S=(X2)Y2S = (X_2)Y_2
其中 X1X_1X2X_2Y1Y_1Y2Y_2 都是正则括号串,且 X1X_1X2X_2 的长度相同。

我们需要证明 X1=X2X_1 = X_2

证明过程

1. 长度相同的含义

由于 X1X_1X2X_2 的长度相同,且它们都是正则括号串,这意味着它们包含相同数量的左括号和右括号。设 X1X_1X2X_2 的长度为 kk,那么它们各自包含 k2\frac{k}{2} 个左括号和 k2\frac{k}{2} 个右括号。

2. 括号匹配的性质

在正则括号串中,每个左括号必须与一个右括号配对,且括号的嵌套是合法的。这意味着在任何前缀中,左括号的数量不能少于右括号的数量。

3. 唯一性表示

正则括号串的唯一性表示意味着,如果两个正则括号串的长度相同,且它们的左括号和右括号的数量相同,那么它们必须是相同的字符串。这是因为正则括号串的结构是由括号的配对和嵌套关系唯一确定的。

4. 归纳法证明
基础情况

k=0k = 0 时,X1X_1X2X_2 都是空串,显然 X1=X2X_1 = X_2

归纳假设

假设对于所有长度小于 kk 的正则括号串,如果它们的长度相同,那么它们必须是相同的字符串。

归纳步骤

考虑长度为 kk 的正则括号串 X1X_1X2X_2。由于 X1X_1X2X_2 的长度相同,且它们都是正则括号串,它们的括号配对关系必须是一致的。因此,X1X_1X2X_2 的递归结构也必须是一样的。根据归纳假设,X1X_1X2X_2 的子结构也必须是相同的。因此,X1=X2X_1 = X_2

有了以上性质, 我们可以考虑类似区间 \(\rm{dp}\) 的做法
具体的, 我们可以把深度为 \(p\)\(X\) 和深度为 \(q\)\(Y\) 拼成深度为 \(\max (p + 1, q)\) 的正则括号串

\(f_{i, k}\) 表示对于长度为 \(2i\) 的括号串, 其中深度为 \(k\) 的方案数

考虑转移

初始化 \(f_{1, 1} = 1\)

\[f_{i, k} \gets \sum_{j = 0}^{i} \sum_{q = 1}^{\min(k, i - j)} f_{j, k - 1} \times f_{i - j, q} + \sum_{j = 0}^{i} \sum_{p = 1}^{\min(k - 1, i - j)} f_{j, k} \times f_{i - j, p} \]

去重

你发现光这样是不够的, 会算重
具体原因可以发现是出现了
(X)Y(X)Y , 其中 (X)(X) 深度和 YY 深度都为 kk , 其会被反复统计
我们考虑记录这种情况是否被统计过即可去重

代码

#include <bits/stdc++.h>

int n, m;
__int128 f[52][64]; //0, 1

template <typename _Tp>
inline void write(_Tp x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}

int main()
{
    freopen("brackets.in", "r", stdin);
    freopen("brackets.out", "w", stdout);
    bool flag[50][61]; //2, 4
    f[0][0] = f[1][1] = 1;
    for (int i = 2; i <= 50; i++)
        for (int k = 1; k <= i; k++) {
            for (int j = 0; j < i; j++) {
                for (int q = 0; q <= std::min(i - j - 1, k); q++) {
                    if (q == k) flag[j][i - j - 1] = true;
                    f[i][k] += f[j][k - 1] * f[i - j - 1][q];
                }
                for (int p = 0; p <= std::min(k - 1, i - j - 1); p++) {
                    if (p == k - 1 && flag[i - j - 1][j]) continue;
                    f[i][k] += f[j][k] * f[i - j - 1][p];
                }
            }
        }

            


    for (int cas = 1; ; cas++) {
        scanf("%d %d", &n, &m);
        if (n == 0 && m == 0) break;
        printf("Case %d: ", cas); write(f[n][m]); printf("\n\n");
    }

    return 0;
}

总结

用于唯一分解正则括号串的性质

常见计数 $\rm{dp}$ 技巧: 去重

我们找到 dp\rm{dp} 会重的部分, 打上标记即可快速处理

posted @ 2025-02-10 21:30  Yorg  阅读(16)  评论(0)    收藏  举报