Description
感觉题意已经挺清晰了,直接戳链接就行了
Solution
先考虑了在一个 \(n\) 个节点的树下接新的节点的计算方法,但发现如果一个树的形态不定那么路径长没法计算,所有接法的路径长之和也不具备什么规律
考虑到一棵树上有两种特殊的点:叶子和根,既然接新节点行不通,那么我们考虑从根把一棵大的二叉树拆成两个小二叉树(可以为空)
尝试了 \(3\) 种状态设计方法,最后还是选择了了一种最容易想到的设法,即设 \(dp_i\) 表示 \(i\) 个点所有可能的方案路径长的总和
但是发现这样的设法有一点点麻烦的地方,就是还要考虑被分开的两个二叉树之间产生的贡献,即我们还要计算一个 \(f_{i,j}\) 数组,表示一个大小为 \(i\) 的二叉树在大小为 \(j\) 的二叉树中产生的贡献,在累加的时候还要多加上 \(f_{i,j}(j-i-1)!+f_{j-i-1,j}i!\)
既然这样,我们不如让 \(dp_i?\) 表示 \(i?\) 个节点的子二叉树对全局(\(n?\) 个节点的二叉树)产生的贡献,那么可以得到
\[
dp_i=\sum\limits_{j=0}^{i-1}\dbinom{i-1}{j}(dp_j(i-j-1)!+dp_{i-j-1}j!+(i-j-1)!j!(j\times (n-j)+(i-j-1)\times (n-i+j+1)))
\]
怎么理解这个方程?
首先,对于一个确定的二叉树,在生成完根节点后我们可以轮流生成它两个子树内的节点,那么一共会有 \(\binom{i-1}{j}\) 种方案(因为两个子二叉树内的生成顺序已经固定)
其次,我们知道 \(dp_i\) 表示的是所有 \(i\) 个节点的二叉树路径长的总和,若用 \(c_j\) 表示第 \(j\) 种 \(i\) 个点的二叉树的路径总长,那么 \(dp_i\) 就相当于是加法原理得到的结果,即
\[
dp_i=\sum\limits_{j=1}^{i!}c_j
\]
其中 \(i!\) 是 \(i\) 个节点一共可以生成 \(i!\) 种二叉树(形态可能有重复)
现在假设我们已经以某种顺序生成了一棵左子树内有 \(j\) 个点,右子树内有 \(i-j-1\) 个点的二叉树
那么它们各自内部对全局产生的贡献就是 \(dp_j(i-j-1)!+dp_{i-j-1}j!\) ,乘上的阶乘意为在一棵子树确定下来的情况下,另一棵子树会有阶乘种子树与之匹配
现在我们还差根节点连向两棵子树的那两条边没有考虑,容易发现它们对全局的贡献就是 \(j\times (n-j)+(i-j-1)\times (n-i+j+1)\) ,还得乘上 \((i-j-1)!j!\),因为两棵子树共有这么多种匹配方案,在每种匹配方案中我们都得加上这个贡献
然后这题就做完了,复杂度 \(O(n^2)\)
其实这道题还是比较抽象的,需要好好想一想
收获大概就是
- 对于按照某种规则生成出来的所有的树(也可能是图之类的),要计算它们的一些值,这种题可以考虑在原来的树上加点,或是把原来的树从树根处拆成两个更小的树(感觉后者可能更适用些)
- 如果某个元素对局部产生的贡献不好算(或者算起来比较麻烦),而最终要求的总体的贡献是要局部的贡献推过来的,那么可以直接考虑这个元素对全局产生的贡献
- 一些比较抽象的题在想的时候一定要理清思路
代码如下:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=2e3+10;
int n,mod,c[N][N],fac[N],dp[N];
inline int MOD(int x){x-=x>=mod? mod:0;return x;}
inline void Add(int &x,int y){x+=y;x-=x>=mod? mod:0;}
inline int Minus(int x){x+=x<0? mod:0;return x;}
int main(){
scanf("%d%d",&n,&mod);
for(register int i=0;i<=n;i++){
c[i][0]=1;
for(register int j=1;j<=i;j++)
c[i][j]=MOD(c[i-1][j]+c[i-1][j-1]);
}
fac[0]=1;for(register int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
for(register int i=1;i<=n;i++)
for(register int j=0;j<i;j++)
Add(dp[i],1ll*c[i-1][j]*MOD(MOD(1ll*dp[j]*fac[i-j-1]%mod+1ll*dp[i-j-1]*fac[j]%mod)+1ll*fac[j]*fac[i-j-1]%mod*MOD(1ll*j*(n-j)%mod+1ll*(i-j-1)*(n-i+j+1)%mod)%mod)%mod);
printf("%d\n",dp[n]);
return 0;
}