题目链接:http://codeforces.com/problemset/problem/724/F
题目大意:给定三个数n,d,mod,求有多少种n个点的不同构的树满足:除了度数为1的结点外,其余结点的度数均为d。答案对质数mod取模。
数据范围:1 ≤ n ≤ 1000, 2 ≤ d ≤ 10, 10^8 ≤ mod ≤ 10^9
题解:看到题目不难想到是树形dp。首先我们考虑有根树,f[i][j][k]表示有 i 个结点,根结点有 j 棵子树,每棵子树大小不超过k的方案数。有两种情况:
1.所有的子树大小都小于k,转移到f[i][j][k-1]。
2.有 l (l>0)棵子树大小为k,结果应为f[i-l*k][j-l][k-1]*C(f[k][d-1][k-1]+l-1,l)。注:C(X+l-1,l)表示从X种方案中选取 l 种,可以重复。
求出了有根树的方案数,我们需要转化成无根树。上述方案会重复计算答案的值,是因为同一棵树选取不同的结点作根时在 f 中表现出的状态是不同的,所以我们应该找到一个特殊的结点作为根,即树的重心,因为树的重心只有一个或两个。我们知道,对于一棵树的重心,它的子树大小不会超过n/2,所以答案为f[n][d][n/2]。而当一棵树有两个重心时,说明这棵树的重心的最大子树恰好有n/2(n为偶数)个点,此时应该减去重复(即两个重心在 f 中表示的状态不同时)的方案数C(f[n/2][d-1][n/2],2)。
最后,当n<=2时没有度数为d的点,此时特判一下就可以了。
时间复杂度O(n^2*d^2)
代码如下:
#include <algorithm>
#include <cstdio>
int f[1005][11][1005],g[1005][11],ine[11],n,d,mo;
int inv(int x)
{
int i=1,y=mo-2;
for (;y;y>>=1,x=1ll*x*x%mo)
if (y&1) i=1ll*i*x%mo;
return i;
}
int main()
{
scanf("%d%d%d\n",&n,&d,&mo);
for (int i=0;i<=n;i++) f[1][0][i]=1;
for (int i=1;i<=d;i++) g[1][i]=1;
for (int i=1;i<=d;i++) ine[i]=inv(i);
for (int i=2;i<=n;i++)
{
for (int j=1;j<=d;j++)
{
for (int k=1;k<i;k++)
for (int l=1;l<=j && l*k<=i;l++)
f[i][j][k]=(f[i][j][k]+1ll*f[i-l*k][j-l][k-1]*g[k][l])%mo;
for (int k=1;k<=n;k++) f[i][j][k]=(f[i][j][k]+f[i][j][k-1])%mo;
}
g[i][1]=f[i][d-1][n];
for (int j=2;j<=d;j++)
g[i][j]=1ll*g[i][j-1]*(f[i][d-1][n]+j-1)%mo*ine[j]%mo;
}
int ans;
if (n<=2) ans=1;
else ans=f[n][d][n/2];
if (n>2 && !(n&1))
ans=(ans-1ll*(f[n/2][d-1][n/2]-1)*f[n/2][d-1][n/2]%mo*ine[2]%mo+mo)%mo;
printf("%d\n",ans);
}