题意:
给你一个
一个
和一个模数,让你求
个点的不同构的无标号树,要求所有除了叶子之外的点的度数都是
的方案数。不同构是指对于任何重标号后的树不同构。
。
题解:
之前没怎么做过这种无标号无根树不同构的题,于是对怎么处理同构上就不怎么会。
这个题的一个重要问题是很多种dp方式都很难处理不同构,于是怎么保证不算重不算漏就很关键了。对于这种无根树,我们在树形dp一般都是转为有根树来做,那么转为有根树的话就要求我们要选出一个根,都是随便选根的话你很难计算当前的每一种方案与多少种方案在重标号之后同构了。
这时候就迎来了本题的关键点,我们选取树上的一个特殊点当做根,我们选重心做根,因为一棵树的重心通常只有一个,只有点数为偶数的时候可能会出现有两个重心的情况。我们考虑选出重心当这个有根树的根有什么用处,其实我们如果再能保证每种方案的所有子树都不同构,就可以不重不漏的计算出答案了。如果有两个重心的话减去重复计算的答案就可以了。
那么转化成有根树之后就比较好进行dp了。我们设 表示有 个点,当前根共有 个子树,每个子树大小不超过 的方案数。我们有 . 解释一下这个式子,就是你子树大小不超过 的可以从都不超过 的转移过来,然后我们可以之前子树都是不超过 ,现在开始是不超过 的了,也就是在当前选了若干个大小是 的子树,而这几个是一个可重组合,于是乘那个组合数。
最后答案就是 ,因为重心的性质,每个子树大小都不超过 ,然后最后还要再减掉 为偶数并且双重心的情况,也就是减掉 。
复杂度的话,我由于组合数那里写的不是很优秀,单次算组合数是 的,于是我写的总复杂度是 的,但是由于跑不满,再加上CF测评机跑得比较快,能过这个题。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,d;
long long mod,dp[1010][11][1010],ni[1000],jie[1000],ans;
inline long long ksm(long long x,long long y)
{
long long res=1;
while(y)
{
if(y&1)
res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
inline long long C(long long x,long long y)
{
if(x<y)
return 0;
long long res=1;
for(long long i=1;i<=y;++i)
res=res*(x-i+1)%mod;
res=res*ni[y]%mod;
return res;
}
int main()
{
scanf("%d%d%I64d",&n,&d,&mod);
if(n<=2)
{
printf("1\n");
return 0;
}
jie[0]=1;
for(int i=1;i<=d;++i)
jie[i]=jie[i-1]*i;
for(int i=1;i<=d;++i)
ni[i]=ksm(jie[i],mod-2);
for(int i=0;i<=n;++i)
dp[1][0][i]=1;
for(int i=2;i<=n;++i)
{
for(int j=1;j<i&&j<=d;++j)
{
for(int k=1;k<=n;++k)
{
dp[i][j][k]=dp[i][j][k-1];
for(int l=1;l*k<i&&l<=j;++l)
{
if(k>1)
dp[i][j][k]=(dp[i][j][k]+dp[i-k*l][j-l][k-1]*C(dp[k][d-1][k-1]+l-1,l)%mod)%mod;
else
dp[i][j][k]=(dp[i][j][k]+dp[i-k*l][j-l][k-1]*C(dp[k][0][k-1]+l-1,l)%mod)%mod;
}
}
}
}
ans=dp[n][d][n/2];
if(n%2==0)
ans=(ans-C(dp[n/2][d-1][n/2],2)+mod)%mod;
printf("%I64d\n",ans);
return 0;
}