1 条题解

  • 0
    @ 2025-2-9 19:05:14

    题目大意

    给定 nn 个 AI,它们之间连成一棵树。对每个节点 ii,若删去它,则会分裂成 did_i 个连通块,大小分别为:

    ci,1,ci,2,…,ci,dic_{i, 1}, c_{i, 2}, \ldots ,c_{i, d_i}

    求所有满足条件的树的数量对 998244353998244353 取模的结果。

    题目解法

    容易发现 did_i 为 ii 节点的度数,那么肯定有:

    ∑i=1ndi=2(n−1).\sum_{i = 1} ^ n d_i = 2(n - 1).

    这一步暂时先放着,待会儿用。

    接下来设 ∃i,j\exists i, j,满足 ii 和 jj 有直接连边,那么:

    • 当「删去 ii」时,jj 所在连通块大小为 ci,x,1≤x≤dic_{i, x}, 1 \le x \le d_i。

    • 当「删去 jj」时,ii 所在连通块大小为 cj,y,1≤y≤djc_{j, y}, 1 \le y \le d_j。

    在一棵树中,删除 ii 和删除 jj 得到的那两个「含有对方的连通块」实际上是互补的:它们加在一起正好包含整棵树的所有 nn 个节点,但由于「删点」各自去掉了 ii 或 jj 本身,所以这两个连通块在原图里其实是不重叠的。由此可得:

    ci,x+cj,y=n.c_{i, x} + c_{j, y} = n.

    那么我们可以得到一个重要结论:若 ii 和 jj 相邻,则必存在一对 (ci,x, cj,y)\bigl(c_{i,x},\,c_{j,y}\bigr) 使它们之和等于 nn。反之,若能在两个点的「子树大小多重集」里分别找到一对数之和为 nn,就可以作为一条 ii 到 jj 的边。

    我们把节点的所有 ci,xc_{i, x} 收集在一起构成一个大小为 ∑i=1ndi=2(n−1)\displaystyle\sum_{i = 1} ^ n d_i = 2(n - 1) 的多重集 SS。树的每条边都会对应一对 (k,n−k)(k, n - k),此时我们得到「边的集合」,再确保第 ii 个点的度数为 did_i 便可求出方案数。

    问题:如何求出方案数?

    我们先令 cntx\text{cnt}_x 为所有 ci,xc_{i, x} 中连通块大小为 xx 的数量,multi,x\text{mult}_{i, x} 为删除第 ii 个点时产生的连通块中大小为 xx 的连通块数量,考虑分为两步求解:

    • 统计「纯粹的」方案数(不考虑重复):

      • 当 k≠n−kk \neq n - k 时,两者必须一一匹对,所以这里的方案数为 cntk!\text{cnt}_k!。因为题目保证有解,所以 cntk\text{cnt}_k 必然等于 cntn−k\text{cnt}_{n - k}。
      • 当 n≡0(mod2)n \equiv 0 \pmod{2} 时,我们需要配对 cntn22\displaystyle\frac{\text{cnt}_{\frac{n}{2}}}{2} 对 (n2,n2)\displaystyle(\frac{n}{2}, \frac{n}{2}),设 $k = \displaystyle\frac{\text{cnt}_{\frac{n}{2}}}{2}$,那么此时的方案数为:
      k!!=k!2k2(k2)!.k!! = \frac{k!}{2^{\frac{k}{2}}(\frac{k}{2})!}.

      这些式子相乘即可求出。

    • 去除重复的方案:

      • 我们发现这个算法会重复计算。举个例子,比如 {2,2,3}\{2, 2, 3\},两个 22 并无顺序可言,无论把「第 11 个 22」匹配给邻居 AA,把「第 22 个 22」匹配给邻居 BB,都对应一组同样的边 {(i,A),(i,B),(i,?)}\{(i, A), (i, B), (i, ?)\},只要这两个邻居在最终树中的度数信息等都吻合,就不能算作两种不同的树。
      • 为了避免这个问题,我们对于每个节点 ii,将答案乘上 1multi,x!\displaystyle\frac{1}{\text{mult}_{i, x}!} 即可。

    综上,我们的答案可以表示为:

    令 MM 为:

    $$\left(\prod_{2k < n}\text{cnt}_k!\right) \times \begin{cases}(\frac{\text{cnt}_{\frac{n}{2}}}{2})!!, & n\text{ is even,}\\[6pt]1, & n\text{ is odd.}\end{cases} $$

    令 DD 为:

    $$\prod_{i = 1} ^ n \prod_{x}\left(\text{mult}_{i, x}!\right) $$

    则答案为 MD\displaystyle\frac{M}{D}。

    复杂度分析

    • 时间复杂度:O(n+log⁡P)O(n + \log P)。

    • 空间复杂度 O(n)O(n)。

    代码实现

    #include <bits/stdc++.h>
    
    #define int long long
    
    constexpr int N = 1e5 + 6, P = 998244353;
    
    int power(int a, int b, int p) {
        int ans = 1;
        a %= p;
        while (b > 0) {
            if (b & 1) {
                ans = ans * a % p;
            }
            a = a * a % p;
            b >>= 1;
        }
        return ans;
    }
    
    void solve() {
        int n;
        std::cin >> n;
        std::vector<int> d(n + 1, 0);
        int sum = 0;
        std::vector<int> freq(n + 1, 0);
        std::vector<std::unordered_map<int, int>> cont(n + 1);
        for (int i = 1; i <= n; ++i) {
            std::cin >> d[i];
            sum += d[i];
            for (int j = 1; j <= d[i]; ++j) {
                int sz;
                std::cin >> sz;
                ++freq[sz];
                ++cont[i][sz];
            }
        }
        int MAX = 2 * (n - 1);
        std::vector<int> fact(MAX + 1, 0), ifact(MAX + 1, 0);
        fact[0] = 1;
        for (int i = 1; i <= MAX; ++i) {
            fact[i] = fact[i - 1] * i % P;
        }
        ifact[MAX] = power(fact[MAX], P - 2, P);
        for (int i = MAX - 1; i >= 0; --i) {
            ifact[i] = ifact[i + 1] * (i + 1) % P;
        }
        int m = 1;
        for (int k = 1; k * 2 < n; ++k) {
            m = m * fact[freq[k]] % P;
        }
        auto so = [&](int x) -> int {
            int res = fact[x];
            res = res * power(power(2, x / 2, P), P - 2, P) % P;
            res = res * ifact[x / 2] % P;
            return res;
        };
        if (n % 2 == 0) {
            int x = freq[n / 2];
            m = m * so(x) % P;
        }
        int dd = 1;
        for (int i = 1; i <= n; ++i) {
            for (auto & kv : cont[i]) {
                dd = dd * fact[kv.second] % P;
            }
        }
        std::cout << m * power(dd, P - 2, P) % P;
    }
    
    int32_t main() {
        std::ios::sync_with_stdio(0);
        std::cin.tie(0), std::cout.tie(0);
    
        int t;
        // std::cin >> t;
        t = 1;
    
        while (t--) {
            solve();
        }
    
        return 0;
    }
    

    信息

    ID
    113
    时间
    3000ms
    内存
    512MiB
    难度
    6
    标签
    递交数
    229
    已通过
    80
    上传者