题目大意
一个
n
个节点的树。在点分治算法中,我们每次寻找树的重心(其作为根各棵子树大小小于等于
当然,在某些情况下,我们在分治的过程中会遇到存在两个重心的情况。
现在我们要求对于所有
n
个节点的树,只有一种点剖方案(点剖过程中不会出现两个重心)的树的个数。
节点是有编号的。两种方案不同当且仅当存在边
答案对
m
取模。
题目分析
首先要弄明白点剖方案合法意味着什么?易证对于点
x
,其实重心且不存在另一个重心当且仅当
一种很显然的思考方向:我们一开始有若干棵满足约束的树,然后我们现在新加入一个点作为重心,将这些树都连接上来,并且我希望现在的树继续合法。
令
fi
表示
i
个节点的合法树个数,
首先
至于 gi ,为了不算重,我们枚举编号最小的节点所在树的大小,用其方案数乘上我们在其中选一个作为根的方案,然后再乘上剩余部分的有根森林方案,以及将 i−1 个节点(最小节点一定在枚举的树中,不需要考虑)放入这棵树的方案。即:
fn 就是答案, O(n2) 计算即可。
代码实现
#include <iostream>
#include <cstdio>
using namespace std;
const int N=3005;
int f[N],g[N];
int C[N][N];
int n,m;
void pre()
{
C[0][0]=1;
for (int i=1;i<=n;i++)
{
C[i][0]=1;
for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%m;
}
}
void dp()
{
g[0]=1;
for (int i=1;i<=n;i++)
{
f[i]=g[i-1];
for (int j=i-1;j*2>=i;j--) (((f[i]-=1ll*j*f[j]%m*g[i-1-j]%m*C[i-1][j]%m)%=m)+=m)%=m;
f[i]=1ll*f[i]*i%m;
for (int j=1;j<=i;j++) (g[i]+=1ll*f[j]*j%m*g[i-j]%m*C[i-1][j-1]%m)%=m;
}
}
int main()
{
freopen("div.in","r",stdin),freopen("div.out","w",stdout);
scanf("%d%d",&n,&m);
pre(),dp();
printf("%d\n",f[n]);
fclose(stdin),fclose(stdout);
return 0;
}