首页 > 其他 > 详细

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

时间:2020-03-01 21:44:25      阅读:59      评论:0      收藏:0      [点我收藏+]

引入

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

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

  • 这里给出无标号有根树和无根树计数的 \(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;
}

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

原文:https://www.cnblogs.com/xyz32768/p/12391772.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!