本文接自此篇博客,旨在记录一些更有趣(?)的内容。
决定完善这篇博客是因为看见了这个题.于是便以这道题目的题解为主线写这篇blog.
\(op=1,2\)的时候的答案可以看上文提到的blog.
无标号有根树计数
\(op=3\)时的问题也就是无标号有根树的计数,在上篇blog里以烷烃计数举例,在这里考虑去掉度数限制后的做法。
记\(f_i\)为\(i\)个点的答案。考虑枚举子树大小以及该大小的子树的数量进行转移,但是复杂度过高。把转移用生成函数的形式写出来的话有:
其意义如下:最前面乘的\(x\)表示根节点,\(\sum_{k\geq 0}x^{ki}\)表示某个大小为\(i\)的子树有几个,它的\(f_i\)次幂则对应着所有大小为\(i\)的子树的方案数。之后再把\(k\)用封闭形式丢掉。
看见这个乘积很烦人,考虑对两边去取\(\ln\).
再同时求个导。
去分母。
看起来没啥花样了?我们把\(f_n\)请回来:
考虑一下后面那个带\(x\)的项是什么,由于\(\frac{1}{1-x^k}=\sum_{i\geq 0}x^{ki}\),那么它再乘上\(x^k\)后,也只有次数是\(k\)的倍数的项的系数为1,于是我们得到了这样子的式子:
稍微整理一下,同时把下标写得更好看些:
记\(g_i=\sum_{j|i}jf_j\),得到了最终的式子:
边界条件\(f_0=0,f_1=1\).
在转移的过程中求出了一个\(f\)的同时维护\(g\),总时间复杂度\(O(n^2)\), 代码见下。
无标号无根树计数
思路和烷基计数差不多?
考虑已经求出了有根树的答案\(f_n\),如何求无根树的答案\(h_n\), 主要的问题还是去掉树的形态相同但根不同的方案数。于是想到对每个形态的树钦点某个点为根。这个点最好还要和树的大小有关,不难想到钦点树的重心为根。
先考虑\(n\)为奇数的情况,此时每棵树有且仅有一个确定的重心,那么如果当前根不是重心的话,它一定有一个大小大于\(\lfloor \frac{n}{2}\rfloor\)的子树,于是有:
接着考虑\(n\)为偶数的情况,稍微麻烦一点的是我们需要考虑当前树有两个重心的情况。如果只有一个重心的话我们只需要和奇数一样减去不合法的情况。而当有两个重心时,这两个点一定通过一条边相连,且把这条边断开后,只有在形成的两棵子树大小一致但形态不同时才会有多算的情况,综上所述可得计算式:
于是就可以做到单个的\(O(1)\)计算了,代码如下:
int solve3()
{
f[0]=0;f[1]=1;
rep(i,1,n) g[i]=1;
rep(i,2,n)
{
f[i]=0;
rep(j,1,i) f[i]=add(f[i],mul(f[j],g[i-j]));
f[i]=mul(f[i],getinv(i-1));
int tmp=mul(f[i],i);
for (int j=i;j<=n;j+=i) g[j]=add(g[j],tmp);
}
return f[n];
}
int C2(int x) {return mul(mul(x,x-1),inv2);}
int solve4()
{
solve3();h[0]=0;h[1]=1;
inv2=getinv(2);
rep(i,2,n)
{
h[i]=0;
rep(j,i/2+1,i-1) h[i]=add(h[i],mul(f[j],f[i-j]));
if (i%2==0) h[i]=add(h[i],C2(f[i/2]));
h[i]=dec(f[i],h[i]);
}
return h[n];
}