无标号有度数限制基环树森林计数 的暴力背包求法

概述

  • 对于给定的 \(n,k\) 我们需要算出,大小为 \(n\) 的内向基环树森林,且入度 \(\le\) \(k\) 的方案数。

  • 为了避免名字冲突,我们令 \(N=n,K=k\)

  • 复杂度为 \(O(n^3\ln n)\)

  • 首先我们算 \(dp_n\) 表示 \(n\) 个点的无标号有根树,在满足度数限制下的方案数。

  • 接着枚举环的大小,用 Burnside 引理,算大小为 \(n\) 的无标号基环树在有度数限制下的方案数,记作 \(tr_n\)

  • 最后,我们统计大小为 \(n\) 的基环树森林 \(ans_n\)

Part 1

\[dp_{n}=[x^{n-1}]\prod_{i=1}^{\infty}(\frac{1}{1-x^i})^{dp_i} \]

  • 唉 还是生成函数能很好的描述这种结构
  • 对于一个树,我们枚举它的所有儿子,构成的子树。
  • \(\frac{1}{1-x^i}=0+x^i+x^{2i}+x^{3i}+\cdots\) 相当于,对于两个儿子,如果他们子树大小相同且结构相同,我们一起考虑,以达到,无标号的目的
  • 而幂上的 \(dp_i\) 其实就是在考虑不同结构,而枚举的 \(i\) 是在考虑不同大小
  • 我们再考虑上度数限制,其实就是多考虑一个变量 \(y\)

\[dp_n=\sum_{k\le K}[x^{n-1}y^k]\prod_{i=1}^{\infty}(\frac{1}{1-x^iy})^{dp_i} \]

\[dp_n=[x^{n-1}y^K](\prod_{i=1}^{\infty}(\frac{1}{1-x^iy})^{dp_i})\cdot\frac{1}{1-y} \]

  • 考虑暴力做背包的复杂度。 \(dp_i\) 会很大的,不能直接枚举,但好在二项式定理的系数可以组合数直接算

\[w_t=[x^{ti}y^t](\frac{1}{1-x^iy})^{dp_i} \]

\[w_t=\binom{t+dp_i-1}{dp_i-1}=\binom{t+dp_i-1}{t} \]

  • 相当于,我们要确定 \(t\) 中有多少是取自那种方案,也就是把 \(t\) 个数分配到 \(dp_i\) 个盒子里,可以为 \(0\) 的个数
  • 注意到 \(dp_i\) 可能很大很大,那我们对 \(p\) 取模还对么

\[\binom{w}{t}=\binom{w/p}{t/p}\binom{w\%p}{t\%p} \]

  • 注意到 \(t\) 其实很小,是远不及 \(p\) 的,所以 \(\binom{w/p}{t/p}=1\),那么我们在求解 \(dp\) 的过程中不断取模是对的。
  • 接下来我们可以考虑复杂度了,我们其实只需要维护一个背包也就是

\[(\prod_{i=1}^{\infty}(\frac{1}{1-x^iy})^{dp_i})\cdot\frac{1}{1-y} \]

  • 而且,求出包含 \(dp_{j}(j<i)\) 的背包之后就可以算 \(dp_i\)
  • \((\frac{1}{1-x^iy})^{dp_i}\) 需要考虑 \(\frac{n}{i}\) 项,也就相当于那么多物品。复杂度就是物品数乘维度。

\[(\sum_{i=1}^n\frac{n}{i})(nk)=\text O(n^2k\ln n) \]

  • 为了给第二部分做准备,我们需要对 \(dp\) 的值做一些改变,因为根结点最后是要在基环树的环上的,那么在环上就有一个入度了。所以最后需要树的儿子个数 \(\le k-1\)。就是按照下面计算就好了

\[dp'_n=[x^{n-1}y^{K-1}](\prod_{i=1}^{\infty}(\frac{1}{1-x^iy})^{dp_i})\cdot\frac{1}{1-y} \]

void solvedp()
{
    static ll f[N][N]; dp[1] = 1;
    for (int i = 0;i <= kk;i++) f[0][i] = 1;
    for (int n = 1;n <= nn;n++)
    {
        vector<node> a;
        for (int j = n, t = 1;j <= nn;j += n, t++)
            a.push_back({ t, C(t + dp[n] - 1, t) });
        for (int x = nn;x >= 0;x--) for (int y = kk;y >= 0;y--)
            for (auto [i, cnt] : a) if (i * n <= x && i <= y)
                (f[x][y] += f[x - i * n][y - i] * cnt) %= mod;
        dp[n + 1] = f[n][kk];
    }
    for (int i = 1;i <= nn;i++) dp[i] = f[i - 1][kk - 1];
}

Part 2

  • 第二部分我们使用 Burnside 引理

\[|X/G|=\frac{1}{|G|}\sum_{g\in G}|X^g| \]

\[tr_n=\sum_{h=1}^n\frac{1}{h}\sum_{t}F(gcd(h,t),\frac{ngcd(h,t)}{h}) \]

\[tr_n=\sum_{h=1}^n\frac{1}{h}\sum_{d|h}[h|nd]F(d,\frac{nd}{h})\varphi(\frac{h}{d}) \]

  • \(F(n,m)\) 表示,给 \(n\) 个位置,每个位置分配一个大小为 \(a_i\) 的树,且 \(\sum a_i=m\),此时有 \(\prod dp_{a_i}\) 种方案
  • 直接暴力第推就好

\[F(n,m)=\sum_{i=0}^ndp_i\cdot F(n-1,m-i) \]

void solvef()
{
    f[0][0] = 1;
    for (int i = 1;i <= nn;i++)
        for (int k = 1;k <= nn;k++)
            for (int t = 1;t <= nn;t++) if (k >= t)
                (f[i][k] += f[i - 1][k - t] * dp[t]) %= mod;
}
void solvetr()
{
    for (int n = 1;n <= nn;n++)
        for (int h = 1;h <= n;h++)
        {
            const ll inv = ksm(h, mod - 2); ll sum = 0;
            for (int d = 1;d <= n;d++) if (h % d == 0 && (n * d) % h == 0)
                sum = (sum + f[d][n * d / h] % mod * phi[h / d]) % mod;
            tr[n] = (tr[n] + sum * inv) % mod;
        }
}

Part 3

  • 最后,我们只需要把若干基环树的方案数,拼成一个森林即可。

\[ans_{n}=[x^{n}]\prod_{i=1}^{\infty}(\frac{1}{1-x^i})^{tr_i} \]

  • 其实就是 Part 1 的式子。至于计算方法,以及为什么对,是一样的
void solveans()
{
    static ll f[N]; f[0] = 1;
    for (int n = 1;n <= nn;n++)
    {
        vector<node> a;
        for (int j = n, t = 1;j <= nn;j += n, t++)
            a.push_back({ t, C(t + tr[n] - 1, t) });
        for (int x = nn;x >= 0;x--)
            for (auto [i, cnt] : a) if (i * n <= x)
                (f[x] += f[x - i * n] * cnt) %= mod;
        ans[n] = f[n];
    }
}
posted @ 2025-05-25 15:03  LUHCUH  阅读(82)  评论(1)    收藏  举报