BZOJ传送门
洛谷传送门
解析:
首先让二叉树带上标号。
我们发现在所有父亲标号小于儿子标号的条件下,所有 n ! n! n!种中序遍历与每个二叉树一一对应。
构建一一对应的方法很简单,找到当前序列的最小值,设为根,然后将当前序列分为左右两部分,递归构造,连上左右儿子。
所以我们要求的就是所有方案中点对的距离和。
现在考虑砍掉根节点变为两棵二叉树。
令
f
i
f_i
fi表示所有
i
i
i个点的带标号二叉树,设根节点深度为
1
1
1情况下的深度之和。
令
g
i
g_i
gi表示所有
i
i
i个点的带标号二叉树,所有点对的距离之和。
则枚举所有左右儿子的构成情况,令
L
L
L表示左儿子的
s
i
z
siz
siz,
R
R
R表示右儿子的
s
i
z
siz
siz,
L
+
R
=
n
−
1
L+R=n-1
L+R=n−1,有:
f
n
=
n
∗
n
!
+
∑
L
=
0
n
−
1
(
n
−
1
L
)
(
f
L
∗
R
!
+
f
R
∗
L
!
)
g
n
=
∑
L
=
0
n
−
1
(
n
−
1
L
)
(
g
L
∗
R
!
+
g
R
∗
L
!
+
f
L
∗
R
!
∗
(
R
+
1
)
+
f
R
∗
L
!
∗
(
L
+
1
)
)
\begin{aligned}f_n&=&&n*n!+\sum_{L=0}^{n-1}{n-1\choose L}(f_L*R!+f_R*L!)\\g_n&=&&\sum_{L=0}^{n-1}{n-1\choose L}(g_L*R!+g_R*L!+f_L*R!*(R+1)+f_R*L!*(L+1))\end{aligned}
fngn==n∗n!+L=0∑n−1(Ln−1)(fL∗R!+fR∗L!)L=0∑n−1(Ln−1)(gL∗R!+gR∗L!+fL∗R!∗(R+1)+fR∗L!∗(L+1))
只需要简单的考虑选择根节点后给左右儿子分配标号就行了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define cs const
int mod;
inline int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(int a,int b){return a<b?a-b+mod:a-b;}
inline int mul(int a,int b){return (ll)a*b%mod;}
cs int N=2003;
int n;
int fac[N],c[N][N];
int f[N],g[N];
signed main(){
cin>>n>>mod;
c[0][0]=fac[0]=1;
for(int re i=1;i<=n;++i){
fac[i]=mul(fac[i-1],i);
c[i][0]=c[i][i]=1;
for(int re j=1;j<i;++j)c[i][j]=add(c[i-1][j],c[i-1][j-1]);
}
f[1]=1;g[1]=0;
for(int re i=2;i<=n;++i){
for(int re L=0,R=i-1;L<i;++L,--R){
f[i]=add(f[i],mul(c[i-1][L],add(mul(f[L],fac[R]),mul(f[R],fac[L]))));
g[i]=add(g[i],mul(c[i-1][L],add(add(mul(g[L],fac[R]),mul(g[R],fac[L])),add(mul(f[L],fac[R+1]),mul(f[R],fac[L+1])))));
}
f[i]=add(f[i],mul(i,fac[i]));
}
cout<<g[n]<<"\n";
return 0;
}