Address
Solution
好像只有我一人用这种无脑做法,果然蒟蒻
首先,一棵
个节点的二叉树中有
个可以插入新叶子节点的位置。
所以一共会生成
棵
个节点的不同的二叉树。
所以,此题从期望题变成了计数题:求生成的所有二叉树的所有无序点对的路径长度之和。
定义状态:
表示
个节点生成的所有二叉树的所有无序点对的路径长度之和。
表示
个节点生成的所有二叉树,根到每个点的路径长度之和。
边界:
而一棵二叉树能被生成,当且仅当每个节点的编号都大于其父亲节点编号。
转移先枚举左子树的点数
,而根必须放最小的编号,所以需要在
个节点中选出
个节点放进左子树,其余的放进右子树,即
。
先讨论
。
(1)先考虑根的左子节点到其子树内所有点的距离和加上根的右子节点到其子树内所有点的距离和。当左子树的形态确定时,右子树不管是
种形态中的哪一种,左子节点到其子树内所有点的距离之和都不变,对于右子树的形态确定时也一样。也就是:
(2)对于整棵树的每一种形态,这棵树除了根之外的
个节点走到根都必须经过连接根和其子节点的恰好一条边。所以需要加上的贡献为:
所以:
的转移比较复杂。
(1)和
一样,把两棵子树的距离之和合并起来:
(2)两个端点分别来自两个子树的路径长度之和。
当左子树确定时,右子树有
种确定方案,左子树内的一个点可以和右子树内的
个点配成路径计入贡献,同时需要考虑:左子树内的每一个点到根时都需要经过连接根的左子节点和其父亲的边。右子树确定时同理。
(3)根到每个点的路径长度,即
在转移完
后加上。
所以:
最后答案
。
Code
非常短。
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
const int N = 2005;
int n, ZZQ, fac[N], C[N][N], f[N], g[N];
int main()
{
int i, j;
cin >> n >> ZZQ;
fac[0] = 1;
For (i, 1, n) fac[i] = 1ll * fac[i - 1] * i % ZZQ;
For (i, 0, n) C[i][0] = 1;
For (i, 1, n) For (j, 1, i)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % ZZQ;
For (i, 1, n)
{
For (j, 0, i - 1)
{
g[i] = (g[i] + (1ll * C[i - 1][j] *
(1ll * g[j] * fac[i - 1 - j] % ZZQ +
1ll * g[i - 1 - j] * fac[j] % ZZQ) % ZZQ % ZZQ) % ZZQ) % ZZQ;
f[i] = (f[i] + 1ll * C[i - 1][j] *
((1ll * f[j] * fac[i - 1 - j] + 1ll * f[i - 1 - j] * fac[j]) % ZZQ
+ (1ll * (g[j] + 1ll * fac[j] * j % ZZQ) *
fac[i - 1 - j] % ZZQ * (i - 1 - j) % ZZQ
+ 1ll * (g[i - 1 - j] + 1ll * fac[i - 1 - j] * (i - 1 - j) % ZZQ)
* fac[j] % ZZQ * j % ZZQ) % ZZQ) % ZZQ) % ZZQ;
}
g[i] = (g[i] + 1ll * (i - 1) * fac[i] % ZZQ) % ZZQ;
f[i] = (f[i] + g[i]) % ZZQ;
}
cout << f[n] << endl;
return 0;
}