CF724F Uniformly Branched Trees
tag:重心,dp,组合计数
晕呼呼地计数...
题意
求 \(n\) 个点的不同的树的个数(同构视为一种,无标号),使得每个点的度数为 \(1\) 或 \(d\)。
\(n\le1000, 2\le d\le10\)
题解
无标号树同构问题一般想到找重心,把重心作为根,这里先假设重心唯一(\(n\) 为奇数)。
设一个树 \(dp\),\(f_{i,j,k}\) 表示当前子树一共有 \(i\) 个节点,当前根有 \(j\) 个儿子,每个儿子的子树大小都不超过 \(k\)。那么答案就是 \(f_{n,d,\left\lfloor\frac n2\right\rfloor}\)。
首先若所有子树都小于 \(k\),递归到 \(f_{i,j,k-1}\)。
第二种情况枚举 \(p\) 个子树大小为 \(k\),递归到 \(f_{i-pk,j-p,k-1}\),转移系数为 \(\binom{f_{k,d-1,k-1}+p-1}p\)。可以把每个子树理解为一个球,方案理解为一个盒子,因为子树之间没有顺序,所以问题对应为“将相同的球放入不同的盒子里,可以为空”的方案数。
然后考虑 \(n\) 为偶数。这时可能会有 \(2\) 个重心,会多算一些方案。比如重心 \(1\) 选择方案 \(a\),重心 \(2\) 选择方案 \(b\);和重心 \(1\) 选择方案 \(b\),重心 \(2\) 选择方案 \(a\),构成的是同一棵树。但是用之前的方法计算时,会把一个重心当作另一个重心的儿子,所以上述的两种方案会被视为两种不同的方案。所以还要减去 \(\binom{f_{\frac n2,d-1,\frac n2-1}}2\)。
注意一下特判 \(n\le2\)。
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void Read(T &n){
char ch; bool flag=false;
while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
if(flag)n=-n;
}
enum{
MAXN = 1005,
D = 15
};
int n, d, MOD, f[D][MAXN][MAXN];
inline int dec(int a, int b){
a -= b;
if(a<0) a += MOD;
return a;
}
inline int ksm(int base, int k=MOD-2){
int res=1;
while(k){
if(k&1)
res = 1ll*res*base%MOD;
base = 1ll*base*base%MOD;
k >>= 1;
}
return res;
}
int inv[D];
inline void prework(int n){
for(register int i=1; i<=n; i++) inv[i] = ksm(i);
}
inline int cn2(int n){return 1ll*n*(n-1)/2%MOD;}
int dp(int n, int d, int k){
if(n==1 and !d) return 1;
if(k==1) return d+1==n;
if(~f[d][n][k]) return f[d][n][k];
int &res = f[d][n][k] = 0;
res = dp(n,d,k-1);
int tmp = dp(k,::d-1,k-1), kkk = tmp;
for(register int i=1; i*k<n and i<=d; i++)
res = (res+1ll*dp(n-i*k,d-i,k-1)*kkk)%MOD,
kkk = 1ll*kkk*(tmp+i)%MOD*inv[i+1]%MOD;
return res;
}
int main(){
Read(n); Read(d); Read(MOD);
memset(f,-1,sizeof f);
prework(d);
if(n<=2) return puts("1"), 0;
if(n&1) printf("%d\n",dp(n,d,n/2));
else printf("%d\n",dec(dp(n,d,n/2),cn2(dp(n/2,d-1,n/2-1))));
return 0;
}