「雅礼集训2018」树

传送门

我们不难发现,\(1\) 号点和 \(2\) 号点肯定是固定了形态的。

那么我们就考虑从这两个节点的状态开始转移。

\(dp_{i, j}\) 表示树的总大小为 \(i\),深度为 \(j\) 的方案数。

那么答案就是 \(\frac{1}{(n - 1)!}\sum_{i = 1} ^ n dp_{n, i} \times i\),根据期望的线性性可得。

那么我们考虑如何转移:

首先我们显然有 \(dp_{1, 1} = dp_{2, 2} = 1\)

对于 \(dp_{i, j}\) ,我们有两种转移:

\[dp_{i, j} = \begin{cases} \sum_{k = 1}^{i - 2} \sum_{l = 1}^k {i - 2 \choose k - 1} \times dp_{k, l} \times dp_{i - k, j}\\ \sum_{k = 1}^{i - 1} \sum_{l = 1}^j {i - 2 \choose k - 1} \times dp_{k, j} \times dp_{i - k, l}\\ \end{cases} \]

第一种转移中,我们考虑枚举 \(2\) 节点的子树(不包括本身)的大小,然后钦定最大深度不在这部分子树里面,第二种转移反之。

记得要用 doubleint 算两次答案。

参考代码:

#include <algorithm>
#include <cstdio>
using namespace std;

const int _ = 30;

int n, p, C[_][_];

namespace task1 {
    double dp[_][_];
	
    double fac(int x) {
        double res = 1.0;
        for (int i = 1; i <= x; ++i) res = res * i;
        return res;
	}
	
    void main() {
        dp[1][1] = dp[2][2] = 1;
        for (int i = 3; i <= n; ++i)
            for (int j = 2; j <= i; ++j) {
                for (int k = 1; k <= i - 2; ++k)
                    for (int l = 1; l <= min(j - 2, k); ++l)
                        dp[i][j] += dp[k][l] * dp[i - k][j] * C[i - 2][k - 1];
                for (int k = 1; k <= i - 1; ++k)
                    for (int l = 1; l <= j; ++l)
                        dp[i][j] += dp[k][j - 1] * dp[i - k][l] * C[i - 2][k - 1];
            }
        double ans = 0;
        for (int i = 1; i <= n; ++i) ans += i * dp[n][i];
        printf("%d\n", (int) (ans / fac(n - 1) + 0.5));
    }
}

namespace task2 {
    int dp[_][_];

    int fac(int x) {
        int res = 1;
        for (int i = 1; i <= x; ++i) res = 1ll * res * i % p;
        return res;
    }
	
    int power(int x, int k) {
        int res = 1;
        for (; k; k >>= 1, x = 1ll * x * x % p)
            if (k & 1) res = 1ll * res * x % p;
        return res % p;
    }

    void main() {
        dp[1][1] = dp[2][2] = 1;
        for (int i = 3; i <= n; ++i)
            for (int j = 2; j <= i; ++j) {
                for (int k = 1; k <= i - 2; ++k)
                    for (int l = 1; l <= min(j - 2, k); ++l)
                        dp[i][j] = (dp[i][j] + 1ll * dp[k][l] * dp[i - k][j] % p * C[i - 2][k - 1] % p) % p;
                for (int k = 1; k <= i - 1; ++k)
                    for (int l = 1; l <= j; ++l)
                        dp[i][j] = (dp[i][j] + 1ll * dp[k][j - 1] * dp[i - k][l] % p * C[i - 2][k - 1] % p) % p;
            }
        int ans = 0;
        for (int i = 1; i <= n; ++i)
            ans = (ans + 1ll * i * dp[n][i] % p) % p;
        printf("%lld\n", 1ll * ans * power(fac(n - 1), p - 2) % p);
    }
}

int main() {
    scanf("%d %d", &n, &p);
    for (int i = 0; i <= n; ++i) C[i][0] = 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= i; ++j)
            C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
    task1 ::main();
    task2 ::main();
    return 0;
}
posted @ 2020-05-16 16:07  Sangber  阅读(224)  评论(0编辑  收藏  举报