zoukankan      html  css  js  c++  java
  • [学习笔记]无标号树的计数

    引入

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

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

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

    无标号有根树计数

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

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

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

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

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

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

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

    • 再求导:

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

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

    • 比较式子两边的系数:

    • [nf_n=f_n+sum_{i>0}if_isum_{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(nlog^2n)) 的复杂度

    无标号无根树计数

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

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

    • [h_n=sum_{i=1}^{lfloorfrac n2 floor}f_if_{n-i} ]

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

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

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

    • 这一部分的处理复杂度是 (O(nlog n)) 的,但求 (f) 的复杂度还是 (O(nlog^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
    ", h[n]);
    	return 0;
    }
    
  • 相关阅读:
    好玩的spring boot banner 图
    数据结构和算法二(数组)
    数据结构与算法三(链表)
    数据结构和算法一(基础知识)
    jenkins 部署node应用
    Docker-compose 安装Jenkins
    Docker 网络模式
    exe4j 转jar
    c#索引器的简单用法
    Adapter模式
  • 原文地址:https://www.cnblogs.com/xyz32768/p/12391772.html
Copyright © 2011-2022 走看看