题目
求大小为(n)的直径为(L)(指边数)的无标号无根树数量。
思考历程
一开始乱推了一波,后来发现会算重。
于是弃疗。
正解
如果(L)为偶数,所有直径的中点是一样的;如果(L)为奇数,所有直径的中边是一样的。
这个挺好证明的。
把无根树定了根之后就好做很多了。先考虑(L)为奇数,把树从中边切开,形成两棵树,这两棵树都要满足最大深度为恰好为(frac{L-1}{2})
设(g_{i,j})为大小为(i),最大深度恰好为(j)的树的个数、
这个东西不好算,就设(f_{i,j})为大小为(i),最大深度至多为(j)的1个数。
转移的时候枚举新增子树大小(k)及其个数(t)。
需要注意的是转移的时候循环变量的枚举顺序,(j)放最外层,(k)放次外层。可以这么理解:把(j)不同的每一层看成个生成函数,计算大小为(k)的子树的贡献的时候给整一层计算贡献,不然容易算重。
搞完之后,枚举左右子树大小即可以求出答案(注意大小相等的情况)。
如果(L)为偶数,相当于以中点为根的树中,根有至少两个儿子的子树的深度恰好为(frac{L}{2}-1)(已经减去了根到儿子的边)。同样像上面一样求出(f)和(g)。
计算答案考虑总体减不合法,总体是(g_{n,frac{L}{2}}),不合法的就是根只有一个儿子的子树深度恰好为(frac{L}{2}-1),枚举其大小(i),贡献即为(g_{i,frac{L}{2}-1}f_{n-i,frac{L}{2}-1})
代码
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 210
#define ll long long
int n,L,mo;
ll inv[N];
ll f[N][N],g[N][N];
void dp(int n,int lim){
for (int j=0;j<=lim;++j)
f[1][j]=1;
for (int j=1;j<=lim;++j)
for (int k=1;k<n;++k){
ll tmp=f[k][j-1];
for (int i=n;i>=2 && i>k;--i){
// if (i==5)
// printf("i=5
");
ll s=tmp;
for (int t=1;k*t<i;++t,s=s*(tmp+t-1+mo)%mo*inv[t]%mo)
(f[i][j]+=f[i-k*t][j]*s)%=mo;
}
}
for (int i=1;i<=n;++i){
g[i][0]=f[i][0];
for (int j=1;j<=lim && j<i;++j)
g[i][j]=(f[i][j]-f[i][j-1]+mo)%mo;
}
// for (int i=1;i<=n;++i,printf("
"))
// for (int j=0;j<=lim && j<i;++j)
// printf("%d ",g[i][j]);
}
int main(){
freopen("exploit.in","r",stdin);
freopen("exploit.out","w",stdout);
scanf("%d%d%d",&n,&L,&mo);
inv[1]=1;
for (int i=2;i<=n;++i)
inv[i]=(mo-mo/i)*inv[mo%i]%mo;
int lim=L/2;
dp(n,lim);
ll ans=0;
if (L&1){
for (int i=lim;n-i>=lim && i<=n-i;++i){
if (i==n-i)
ans+=g[i][lim]*(g[i][lim]+1)%mo*inv[2]%mo;
else
ans+=g[i][lim]*g[n-i][lim]%mo;
}
ans%=mo;
}
else{
ans=g[n][lim];
if (lim>=2)
for (int i=lim;i<n;++i)
ans-=g[i][lim-1]*f[n-i][lim-1]%mo;
ans=(ans%mo+mo)%mo;
}
printf("%lld
",ans);
return 0;
}
总结
见到无根树的时候要想办法给它定个根。
什么直径的中点中边,或者重心之类的。