CF1089I Interval-Free Permutations

标签:析合树

析合树就是用来处理这一种值域连续段的问题的。

OI-wiki 上对于析合树的讲解。

我们回顾一下题目,要求不存在长度为 \([2, n - 1]\) 之间的连续段,换句话说,就是根节点下恰有 \(n - 1\) 个节点,且没有任何一个子段是题目中要求的连续段。

我们记这样的答案为 \(A_n\) 也就是我们要求的答案。直接解决是 Hard 的,我们考虑容斥一下,算存在连续段的方案数。

首先需要分类讨论:

根是合点

不妨以递增顺序的合点为例,显然递减的情况是一样的。

\(I_x\) 表示前 \(x\) 位有多少种填法,使得任意一个前缀都不是连续段。

\[I_x = \sum_{i = 1}^{x - 1} I_i(x - i)! \]

前面方案乘上后面随便的方案之和即可。

这一种情况的答案就是 \(2\sum_{i = 1}^{n} I_i(n - i)!\)

根是析点

这一步我们要知道子树中任意一个子段都不是连续段,这个就是我们之前设的 \(A\) 数组,同样考虑递推求出来。

首先要枚举有几个儿子,然后乘上这些段的答案即可。

那么我们还需要 DP 计算出 \(n\) 个点放在 \(j\) 段中的方案。

然后递推出 \(A\) 数组即可。

代码

点击查看代码
#include <bits/stdc++.h>
#define int long long

#define F(i, a, b) for (int i = (a); i <= (b); i++)
#define dF(i, a, b) for (int i = (a); i >= (b); i--)
using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 405, M = (N << 1), inf = 1e16;
int mod, n, m, A[N], I[N], fac[N], B[N][N];
signed main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> mod;
    fac[0] = 1;
    F(i, 1, 400) I[i] = fac[i] = fac[i - 1] * i % mod;
    F(i, 2, 400)
        F(j, 1, i - 1) 
            I[i] = (I[i] - I[j] * fac[i - j] % mod + mod) % mod;
    B[0][0] = 1;
    F(i, 1, 400) F(j, 1, i) F(k, 1, i)
        B[i][j] = (B[i][j] + B[i - k][j - 1] * fac[k] % mod) % mod;
    A[1] = 1, A[2] = 2, A[3] = 0;
    F(i, 4, 400) {
        A[i] = fac[i];
        F(j, 1, i - 1)
            A[i] = (A[i] - 2 * I[j] * fac[i - j] % mod + mod) % mod;
        F(j, 4, i - 1)
            A[i] = (A[i] - B[i][j] * A[j] % mod + mod) % mod;
    }
    F(i, 1, n) {
        int x;
        cin >> x;
        cout << A[x] << '\n';
    }
    return 0;
}
posted @ 2024-05-26 12:13  Z_t_r  阅读(7)  评论(0编辑  收藏  举报