本文接自此篇博客,旨在记录一些更有趣(?)的内容。
决定完善这篇博客是因为看见了这个题.于是便以这道题目的题解为主线写这篇blog.
(op=1,2)的时候的答案可以看上文提到的blog.
无标号有根树计数
(op=3)时的问题也就是无标号有根树的计数,在上篇blog里以烷烃计数举例,在这里考虑去掉度数限制后的做法。
记(f_i)为(i)个点的答案。考虑枚举子树大小以及该大小的子树的数量进行转移,但是复杂度过高。把转移用生成函数的形式写出来的话有:
其意义如下:最前面乘的(x)表示根节点,(sum_{kgeq 0}x^{ki})表示某个大小为(i)的子树有几个,它的(f_i)次幂则对应着所有大小为(i)的子树的方案数。之后再把(k)用封闭形式丢掉。
看见这个乘积很烦人,考虑对两边去取(ln).
再同时求个导。
去分母。
看起来没啥花样了?我们把(f_n)请回来:
考虑一下后面那个带(x)的项是什么,由于(frac{1}{1-x^k}=sum_{igeq 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} floor)的子树,于是有:
接着考虑(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];
}