Loading

P8329 [ZJOI2022] 树

直接求是困难的,所以考虑容斥将所求容斥为两部分:每个结点至少在一棵树上为叶子的方案数 - 至少有一个结点在两棵树上都为叶子的方案数。

考虑 DP,设 \(f_i(x, y)\) 表示 \([1, i]\) 中是第一棵树的非叶子的结点数为 \(x\)\([i + 1, n]\) 中是第二棵树的非叶子的结点数为 \(y\) 时的方案数。

每次新增一个点,分讨转移:

  • \(i\) 在第一棵树上是非叶子,在第二棵树上是叶子:

\[f_i(x, y) \gets f_i(x, y) + f_{i-1}(x-1, y) \times (x-1)y \]

  • \(i\) 在第一棵树上是叶子,在第二棵树上是非叶子:

\[f_i(x, y) \gets f_i(x, y) + f_{i-1}(x, y+1) \times xy \]

  • \(i\) 在两棵树上都是叶子:

    这里其实还有两种情况:

    • \(i\) 在第一棵树上是叶子,钦定它在第二棵树上是叶子。
    • \(i\) 在第二棵树上是叶子,钦定它在第一棵树上是叶子。

    但因为结果相同,所以把两种情况并在一起转移,即

\[f_i(x, y) \gets f_{i-1}(x, y) \times (-2xy) \]

初值:\(\forall y \in [1, n - 1], f_1(1, y) = y\)

答案:\(ans_n = \sum\limits_{i=1}^{n-2} f_{n-1}(x, 1) \times x\)

时间复杂度 \(\mathcal O(n^3)\)

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

constexpr int N = 510;

int n, MOD;
ll pf[N][N], cf[N][N];

int main() {
    ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
    cin >> n >> MOD;
    for (int i = 1; i < n; i++) pf[1][i] = i;
    for (int i = 2; i <= n; i++) {
        ll ans = 0;
        for (int x = 1; x < i; x++) ans += pf[x][1] * x;
        cout << (ans % MOD + MOD) % MOD << '\n';
        for (int x = 1; x <= i; x++) {
            for (int y = 1; x + y <= n; y++) {
                cf[x][y] = (pf[x - 1][y] * (x - 1) * y + pf[x][y + 1] * x * y - ((pf[x][y] * x * y) << 1)) % MOD;
            }
        }
        memcpy(pf, cf, sizeof(pf));
    }
    return 0;
}
posted @ 2024-02-21 19:20  Chy12321  阅读(14)  评论(0编辑  收藏  举报