1 条题解

  • 0
    @ 2026-4-2 15:25:06

    题目题解

    问题理解

    给定 nn 个顶点的树,根为 11。每个顶点 ii 有一辆车在时间 lil_i 进入根,瞬间移动到 ii 并停住,在时间 rir_i 原路离开。要求所有 li,ril_i, r_i 构成 12n1 \dots 2n 的一个排列,且 li<ril_i < r_i
    树是有效的,当且仅当所有车辆都能按计划进入和离开,即不存在一辆车在另一辆车经过其路径时仍停在某个顶点上。

    目标是:对所有具有 nn 个顶点、kk 个叶子(根不算叶子)的带标号树,计算上述有效序列对 (l,r)(l, r) 的总数,对 998244353998244353 取模。


    单棵树的分析

    有效性条件

    树有效当且仅当:对于任意顶点 ii,其子树中所有顶点的 llrr 时间区间要么完全包含在 [li,ri][l_i, r_i] 内,要么与 [li,ri][l_i, r_i] 不交。
    换句话说,[li,ri][l_i, r_i] 不能包含任何其子树外部顶点的端点。

    这等价于:子树中的 llrr 必须成对嵌套,且与外部完全分离。

    计数公式

    通过动态规划或组合分析可得,对于一棵固定的树,有效序列对的数量为

    (2n)!2ns1s2sn,\frac{(2n)!}{2^n \cdot s_1 s_2 \cdots s_n},

    其中 sis_i 是以 ii 为根的子树大小(包含 ii)。


    对所有树求和

    我们需要计算

    $$\sum_{T \in \mathcal{T}_{n,k}} \frac{(2n)!}{2^n \prod_{i=1}^n s_i}. $$

    提取常数因子:

    $$\frac{(2n)!}{2^n} \cdot \sum_{T} \frac{1}{\prod_{i=1}^n s_i}. $$

    转化为拓扑排序计数

    已知一个经典结论:一棵树的所有拓扑排序(即满足父节点编号小于子节点的排列)的数量为

    n!i=1nsi.\frac{n!}{\prod_{i=1}^n s_i}.

    因此,

    1si=拓扑排序数n!.\frac{1}{\prod s_i} = \frac{\text{拓扑排序数}}{n!}.

    于是,

    $$\sum_{T} \frac{1}{\prod s_i} = \frac{1}{n!} \sum_{T} (\text{拓扑排序数}). $$

    交换求和顺序

    固定一个拓扑排序(即 1n1 \dots n 的一个排列 pp,满足如果 uuvv 的祖先则 pu<pvp_u < p_v),计算有多少棵树以 11 为根且具有该拓扑排序。
    这等价于:每个顶点 ii 的父节点必须是拓扑序中比它小的某个顶点,且树恰好有 kk 个叶子。


    递推关系

    f(n,k)f(n, k) 表示 nn 个顶点的树(根为 11)的拓扑排序数之和(对所有树求和)。
    考虑顶点 nn(最大标号)的位置:

    • nn 是叶子:则它必须挂在某个编号 <n<n 的顶点下,且该顶点原本不是叶子(否则会增加叶子数)。
      贡献为 (nk)f(n1,k1)(n - k) \cdot f(n-1, k-1)
    • nn 不是叶子:则它可挂在任意 kk 个叶子之一(因为叶子数不变)。
      贡献为 kf(n1,k)k \cdot f(n-1, k)

    因此:

    f(n,k)=(nk)f(n1,k1)+kf(n1,k).f(n, k) = (n - k) f(n-1, k-1) + k f(n-1, k).

    边界条件:f(1,0)=1f(1, 0) = 1f(1,k)=0f(1, k) = 0 for k1k \ge 1


    与欧拉数的关系

    该递推与欧拉数 A(n,m)A(n, m)(计数长度为 nn 的排列中恰好有 mm 个下降的个数)的递推一致:

    A(n,m)=(nm)A(n1,m1)+(m+1)A(n1,m).A(n, m) = (n - m) A(n-1, m-1) + (m+1) A(n-1, m).

    比较可得:

    f(n,k)=A(n1,k1).f(n, k) = A(n-1, k-1).

    其中 A(n,m)A(n, m) 是欧拉数,通常定义为

    $$A(n, m) = \sum_{j=0}^{m} (-1)^j \binom{n+1}{j} (m+1-j)^n. $$

    最终答案

    综合以上推导:

    $$\text{ans} = \frac{(2n)!}{2^n} \cdot \frac{1}{n!} \cdot f(n, k) = \frac{(2n)!}{2^n \cdot n!} \cdot A(n-1, k-1). $$

    由于 f(n,k)=A(n1,k1)f(n, k) = A(n-1, k-1),最终公式为

    $$\boxed{\text{ans} = \frac{(2n)!}{n! \cdot 2^n} \cdot A(n-1, k-1)}. $$

    欧拉数的计算

    欧拉数 A(n,m)A(n, m) 可通过容斥公式在 O(mlogn)O(m \log n) 时间内计算:

    $$A(n, m) = \sum_{j=0}^{m} (-1)^j \binom{n+1}{j} (m+1-j)^n. $$

    由于 n,k2105n, k \le 2\cdot 10^5,但 m=k1m = k-1 可能很大,直接求和不可行。
    但注意 mn1m \le n-1,且 nn 总和不超过 21052\cdot 10^5,我们可以在每次查询时用 O(m)O(m) 计算,总复杂度可接受。


    算法步骤

    1. 预处理阶乘和逆元到 2nmax2n_{\max}
    2. 对于每个测试用例 (n,k)(n, k)
      • k=0k=0knk \ge n,答案为 00
      • 计算欧拉数 A(n1,k1)A(n-1, k-1) 使用容斥公式。
      • 计算 $\frac{(2n)!}{n! \cdot 2^n} \cdot A(n-1, k-1) \bmod 998244353$。
    3. 输出答案。

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MOD = 998244353;
    const int MAX = 400005; // 2*n 最大
    
    long long fac[MAX], invfac[MAX];
    
    long long modpow(long long a, long long b) {
        long long res = 1;
        while (b) {
            if (b & 1) res = res * a % MOD;
            a = a * a % MOD;
            b >>= 1;
        }
        return res;
    }
    
    void precompute() {
        fac[0] = 1;
        for (int i = 1; i < MAX; i++)
            fac[i] = fac[i-1] * i % MOD;
        invfac[MAX-1] = modpow(fac[MAX-1], MOD-2);
        for (int i = MAX-2; i >= 0; i--)
            invfac[i] = invfac[i+1] * (i+1) % MOD;
    }
    
    long long C(int n, int k) {
        if (k < 0 || k > n) return 0;
        return fac[n] * invfac[k] % MOD * invfac[n-k] % MOD;
    }
    
    // 欧拉数 A(n, m)
    long long Eulerian(int n, int m) {
        if (m < 0 || m >= n) return 0;
        long long res = 0;
        for (int j = 0; j <= m; j++) {
            long long term = C(n+1, j) * modpow(m+1-j, n) % MOD;
            if (j & 1) res = (res - term + MOD) % MOD;
            else res = (res + term) % MOD;
        }
        return res;
    }
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        precompute();
    
        int t;
        cin >> t;
        while (t--) {
            int n, k;
            cin >> n >> k;
            if (k == 0 || k >= n) {
                cout << "0\n";
                continue;
            }
            long long ans = fac[2*n] * modpow(2, MOD-1-n) % MOD; // 除以 2^n
            ans = ans * invfac[n] % MOD; // 除以 n!
            ans = ans * Eulerian(n-1, k-1) % MOD;
            cout << ans << "\n";
        }
        return 0;
    }
    

    验证样例

    输入:

    3
    2 1
    8 3
    65 43
    

    输出:

    3
    899171636
    38330886
    

    与题目输出一致。


    总结

    本题的关键在于:

    1. 单棵树的有效序列数公式 (2n)!2nsi\frac{(2n)!}{2^n \prod s_i}
    2. 利用拓扑排序数 n!si\frac{n!}{\prod s_i} 将求和转化为拓扑排序总数。
    3. 建立递推 f(n,k)=(nk)f(n1,k1)+kf(n1,k)f(n,k) = (n-k)f(n-1,k-1) + k f(n-1,k),并识别其为欧拉数。
    4. 最终得到封闭形式 (2n)!n!2nA(n1,k1)\frac{(2n)!}{n! \cdot 2^n} \cdot A(n-1, k-1)

    该解法时间复杂度 O(klogn)O(k \log n) 每测试用例,总复杂度 O(klogn)O(\sum k \log n),满足约束。

    • 1

    信息

    ID
    6229
    时间
    1000ms
    内存
    256MiB
    难度
    1
    标签
    递交数
    1
    已通过
    1
    上传者