「解题报告」ARC137F Overlaps

首先有经典结论,两个实数随机变量相等的概率为 \(0\)。那么在实数上随机若干个数,最后肯定会有一个顺序关系,而我们只关心这个顺序,所以可以把问题变成离散的。

也就是我们现在有 \(2n\) 个点,每次随机选取两个点覆盖,求覆盖不超过 \(k\) 次的概率。

我们可以把这 \(2n\) 个点看做不一定合法的括号序列,我们只需要保证左括号在右括号的左边,并且 \(n\) 种括号可以随便排列,所以总方案数就是 \(\frac{(2n)!}{2^nn!}\)

考虑如何计数这个东西,我们可以从左往右填,每碰到一个左括号直接填,当碰到右括号时,我们可以将这个右括号与现在剩下的任意一个左括号匹配,所以一种括号序列的方案数就是每个右括号的深度的乘积。把右括号的深度作为序列拿出来,发现该序列满足以下性质:

  • \(1 \le x_i \le k\)
  • \(x_i \ge x_{i - 1} - 1\)
  • \(x_n = 1\)

并且我们发现只要满足这几个条件的序列就能构造出一个相对应的括号序列。所以我们考虑计算所有这样的序列的 \(\prod x_i\) 之和。

最难搞的是第二个条件,考虑用二项式反演容斥掉第二个条件。我们先不管第二个条件,然后将一个序列从 \(x_i \ge x_{i - 1} - 1\) 的位置划分开,这样就得到了若干个 \(x_i \le x_{i - 1} - 2\) 的序列,而后者是容易计数的。所以我们考虑钦定至少 \(i\) 个位置是错误的方案数为 \(f_i\),那么答案就是 \(\sum_{i=0}^n (-1)^i f_i\)

先考虑怎么计算一整块都满足 \(x_i \le x_{i - 1} - 2\) 的方案数,这个相当于在 \([1, k]\) 中选数,不能选相邻的两个数,可以分治去计算,考虑 \(dp_{l, r, 0/1, 0/1, j}\)\([l, r]\) 区间内选 \(j\) 个数,\(l, r\) 分别选没选的方案数,FFT 合并即可。

再考虑怎么求拼接在一起的方案数,这个比较经典,写出生成函数,那么答案就是 \(\frac{1}{1-F}\)。(或者分治 FFT 计算)

还有一个 \((-1)^i\),直接把 \(\frac{1}{1-F(x)}\) 改成 \(\frac{1}{1-F(-x)}\) 就行了。

别忘了还有第三个条件,所以需要将 \(\frac{1}{1-F(-x)}\) 与选择了 \(1\) 的方案数卷起来。

int n, k;
array<array<Polynomial, 2>, 2> solve(int l, int r) {
    array<array<Polynomial, 2>, 2> ret;
    ret[0][0] = ret[0][1] = ret[1][0] = ret[1][1] = {0};
    if (l == r) {
        ret[0][0] = {1};
        ret[0][1] = {0};
        ret[1][0] = {0};
        ret[1][1] = {0, l};
        return ret;
    }
    int mid = (l + r) >> 1;
    auto L = solve(l, mid), R = solve(mid + 1, r);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            for (int a = 0; a < 2; a++) {
                for (int b = 0; a + b < 2; b++) {
                    ret[i][j] = ret[i][j] + L[i][a] * R[b][j];
                }
            }
        }
    }
    return ret;
}
int main() {
    scanf("%d%d", &n, &k);
    auto ans = solve(1, k);
    Polynomial a, b;
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 2; j++) {
            if (i == 1) a = a + ans[i][j];
            b = b + ans[i][j];
        }
    }
    for (int i = 0; i <= a.len; i += 2) {
        a[i] = 1ll * a[i] * (P - 1) % P;
    }
    for (int i = 0; i <= b.len; i += 2) {
        b[i] = 1ll * b[i] * (P - 1) % P;
    }
    a = a * (1 - b).inv(n + 1);
    int tot = 1;
    for (int i = 1; i <= n; i++) {
        tot = 1ll * tot * (2 * i - 1) % P;
    }
    printf("%lld\n", 1ll * a[n] * qpow(tot, P - 2) % P);
    return 0;
}
posted @ 2023-01-29 11:20  APJifengc  阅读(75)  评论(0编辑  收藏  举报