[学习笔记]无标号树的计数

引入

  • 定义两棵无标号有根树相同,当且仅当存在一个给这两棵树点编号的方案(根的编号必须相同),使得编号后的两棵树相同

  • 定义两棵无标号无根树相同,当且仅当存在一个给这两棵树点编号的方案,使得编号后的两棵树相同

  • 这里给出无标号有根树和无根树计数的 \(O(n\log^2n)\) 做法

无标号有根树计数

  • \(f_n\)\(n\) 个点无标号有根树的个数

  • 不难发现两棵有根树同构,当且仅当存在一个 \((i,j)\),满足根的大小为 \(i\) 且形态为 \(j\)(我们把大小为 \(i\) 的有根树的形态依次编号为 \(1\)\(f_i\))的子树个数不同

  • \(f\) 的 OGF 为 \(F(x)\),我们有以下式子:

  • \[F(x)=x\prod_{i>0}(\frac1{1-x^i})^{f_i} \]

  • 式子右边第一个 \(x\) 表示根,\(i\) 表示子树大小,\(\frac1{1-x^i}\) 表示对于一种大小为 \(i\) 的指定形态的子树,这种子树占用 \(i\times j\)\(j\ge 0\))个点的方案数为均 \(1\)\((\frac1{1-x^i})^{f_i}\) 自然就表示大小为 \(i\) 的所有形态的子树

  • 这种多个多项式乘积的式子我们通常采用取对数的方法:

  • \[\ln F(x)=\ln x-\sum_{i>0}f_i\ln(1-x^i) \]

  • 再求导:

  • \[\frac{F'(x)}{F(x)}=\frac 1x+\sum_{i>0}if_i\frac{x^{i-1}}{1-x^i} \]

  • \[xF'(x)=F(x)+F(x)\sum_{i>0}if_i\frac{x^i}{1-x^i} \]

  • 比较式子两边的系数:

  • \[nf_n=f_n+\sum_{i>0}if_i\sum_{j>0,i|j}f_{n-j} \]

  • \(g_i=\sum_{j|i}jf_j\),那么我们有:

  • \[(n-1)f_n=\sum_{i>0}f_ig_{n-i} \]

  • \(f\) 用分治 FFT 求,每求出一个 \(f\) 之后都枚举倍数更新 \(g\),即可做到 \(O(n\log^2n)\) 的复杂度

无标号无根树计数

  • 由于一棵树的重心只有 \(1\)\(2\) 个,所以我们考虑把根定义为重心

  • 考虑容斥,用 \(f_n\) 减去根不是重心的方案数:

  • \[h_n=\sum_{i=1}^{\lfloor\frac n2\rfloor}f_if_{n-i} \]

  • 注意特殊情况:当 \(n\) 是偶数时重心可以有两个,当 \(i=\frac n2\) 时,我们可以强制把重心边删掉之后,根所在的子树形态编号不小于另一个,故减去 \(\binom{f_i}2\) 即可

  • 如果需要预处理所有的 \(h\),由于 \(i\le\frac n2\) 相当于 \(i\le n-i\),而 \(i<n-i\)\(i>n-i\) 是对称的,所以把多项式 \(F\) 平方之后每项系数除以 \(2\),再特殊处理一下偶数的情况即可:

  • \[h_n=f_n-\frac 12([x_n]F(x)^2-f_{\frac n2}[n\bmod 2=0]) \]

  • 这一部分的处理复杂度是 \(O(n\log n)\) 的,但求 \(f\) 的复杂度还是 \(O(n\log^2n)\)

Code(无标号无根树)

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

template <class T>
inline T Min(const T &a, const T &b) {return a < b ? a : b;}

const int N = 2e5 + 5, M = 8e5 + 5, djq = 998244353, I2 = 499122177;

int f[N], g[N], h[N], inv[N], rev[M], yg[M], A[M], B[M], ff, tot;

inline void add(int &a, const int &b) {if ((a += b) >= djq) a -= djq;}

inline void sub(int &a, const int &b) {if ((a -= b) < 0) a += djq;}

int qpow(int a, int b)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % djq;
		a = 1ll * a * a % djq;
		b >>= 1;
	}
	return res;
}

void FFT(int n, int *a, int op)
{
	for (int i = 0; i < n; i++) if (i < rev[i]) std::swap(a[i], a[rev[i]]);
	yg[n] = qpow(3, (djq - 1) / n * ((n + op) % n));
	for (int i = n >> 1; i; i >>= 1)
		yg[i] = 1ll * yg[i << 1] * yg[i << 1] % djq;
	for (int k = 1; k < n; k <<= 1)
	{
		int x = yg[k << 1];
		for (int i = 0; i < n; i += k << 1)
		{
			int w = 1;
			for (int j = 0; j < k; j++)
			{
				int u = a[i + j], v = 1ll * w * a[i + j + k] % djq;
				add(a[i + j] = u, v); sub(a[i + j + k] = u, v);
				w = 1ll * w * x % djq;
			}
		}
	}
	if (op == -1)
	{
		int gg = qpow(n, djq - 2);
		for (int i = 0; i < n; i++) a[i] = 1ll * a[i] * gg % djq;
	}
}

void polymul(int n, int m)
{
	ff = 1; tot = 0;
	while (ff <= n + m) ff <<= 1, tot++;
	for (int i = 0; i < ff; i++)
		rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << tot - 1);
	for (int i = n + 1; i < ff; i++) A[i] = 0;
	for (int i = m + 1; i < ff; i++) B[i] = 0;
	FFT(ff, A, 1); FFT(ff, B, 1);
	for (int i = 0; i < ff; i++) A[i] = 1ll * A[i] * B[i] % djq;
	FFT(ff, A, -1);
}

void czx(int l, int r)
{
	if (l == r)
	{
		if (l == 1) f[r] = 1; else f[r] = 1ll * f[r] * inv[l - 1] % djq;
		for (int i = l; i <= 200000; i += l)
			g[i] = (1ll * l * f[r] + g[i]) % djq;
		return;
	}
	int mid = l + r >> 1, l1 = mid - l, l2 = Min(r - l, mid);
	czx(l, mid);
	for (int i = 0; i <= l1; i++) A[i] = f[l + i];
	for (int i = 0; i <= l2; i++) B[i] = g[i];
	polymul(l1, l2);
	for (int i = mid + 1; i <= r; i++) if (i - l < ff) f[i] = (f[i] + A[i - l]) % djq;
	l2 = Min(r - l, l - 1);
	for (int i = 0; i <= l1; i++) A[i] = g[l + i];
	for (int i = 0; i <= l2; i++) B[i] = f[i];
	polymul(l1, l2);
	for (int i = mid + 1; i <= r; i++) if (i - l < ff) f[i] = (f[i] + A[i - l]) % djq;
	czx(mid + 1, r);
}

int main()
{
	int T, n;
	inv[1] = 1;
	for (int i = 2; i <= 200000; i++)
		inv[i] = 1ll * (djq - djq / i) * inv[djq % i] % djq;
	czx(1, 200000); read(T);
	for (int i = 0; i <= 200000; i++) A[i] = B[i] = h[i] = f[i];
	polymul(200000, 200000);
	for (int i = 1; i <= 200000; i++)
		h[i] = (f[i] - 1ll * I2 * (A[i] - (i & 1 ? 0 : f[i >> 1])
			+ djq) % djq + djq) % djq;
	while (T--) read(n), printf("%d\n", h[n]);
	return 0;
}
posted @ 2020-03-01 20:18  epic01  阅读(706)  评论(0编辑  收藏  举报