[CF724F] Uniformly Branched Trees
链接
题目大意
求由 \(n\) 个节点构成且节点度数只为 \(1\) 或 \(d\) 的本质不同无根树数量。
\(n\leq 1000,d\leq 10\)
题解
这个数据范围一看就很 \(O(n^2d)\) 或 \(O(n^2d^2)\)。
首先假设这是一颗有根树。考虑dp,令 \(f_{i,j,k}\) 表示 \(i\) 个节点的树中,根有 \(j\) 个儿子,其余节点有 \(d\) 个儿子。其中所有儿子大小不超过 \(k\)。
对于有根树来说,同构相当于存在一种子树的对应关系。可以简单认为就是把子树按一定的顺序排序,然后一一对应。
大小显然可以作为第一维排序。所以我们把大小为 \(k\) 的儿子全部找出来,不妨假设有 \(t\) 个,那么剩下的状态就是 \(f_{i-t\cdot k,j-t,k-1}\)。
考虑转移过程的系数是多少。首先我们知道大小为 \(k\) 的本质不同合法子树数量是 \(f_{k,d-1,k-1}\)。注意当 \(k=0\) 时应当特判,此时一定只有1种方案。
可以发现这样等同于“有 \(t\) 个相同小球,放进 \(f_{k,d-1,k-1}\) 个不同的盒子中的方案数”,这个就是经典的隔板法,方案数为 \(\binom{f_{k,d-1,k-1}+t-1}{t}\)。
所以得到递推方程:\(\displaystyle f_{i,j,k}=\sum_{t}{f_{i-t\cdot k,j-t,k-1}\times\binom{f_{k,d-1,k-1}+t-1}{t}}\)。注意特判 \(t=0\) 时右边式子应当为1。
最后考虑同构怎么处理:首先我们应当钦定一个点为根,这里显然选重心最方便,因为重心的dp值对应就是 \(f_{n,d,\frac n 2}\)。
当 \(n\) 为奇数时这个dp值一定正确,因为一棵树的重心只有一个,不会记重。
但是当 \(n\) 为偶数时会有计重,因为这时一棵树的重心有两个。即如果我们钦定其中一个为根,那么两个重心之间的同构会被忽略。
但是可以发现,如果我们将两个重心之间的边断掉,一定会形成两个 \(\frac n 2\) 大小的树。这种情况下,如果两个树不棵构,交换两棵树就是另一种方案。
所以有两个重心且两棵树不互相同构的方案数为 \(n\times (n-1)\),所以最后方案数应减去 \(\frac {n(n-1)} 2\)。
复杂度 \(O(n^2d^2)\)。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1010
#define D 12
using namespace std;
int fac[N],inv[N],mod;
int ksm(int a,int b=mod-2)
{
int r=1;
for(;b;b>>=1)
{
if(b&1) r=1ll*r*a%mod;
a=1ll*a*a%mod;
}
return r;
}
int C(int a,int b)
{
int r=inv[b];
for(b--;b>=0;b--) r=1ll*r*(a-b)%mod;
return r;
}
int f[N][D][N];
int main()
{
int n,d;
scanf("%d%d%d",&n,&d,&mod);
if(n<=2){puts("1");return 0;}
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=ksm(fac[n]);
for(int i=n-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
for(int i=0;i<=n;i++) f[1][0][i]=1;
for(int i=2;i<=n;i++)
for(int j=1;j<=min(d,i-1);j++)
for(int k=1;k<=n;k++)
{
f[i][j][k]=f[i][j][k-1];
for(int t=1;t*k<=i && t<=j;t++)
f[i][j][k]=(f[i][j][k]+1ll*f[i-t*k][j-t][k-1]*(k==1?1:C(f[k][d-1][k-1]+t-1,t))%mod)%mod;
}
printf("%d\n",(f[n][d][n/2]-((n&1)?0:C(f[n/2][d-1][n/2-1],2))+mod)%mod);
return 0;
}

浙公网安备 33010602011771号