1 条题解
-
0
题目题解
问题理解
给定 个顶点的树,根为 。每个顶点 有一辆车在时间 进入根,瞬间移动到 并停住,在时间 原路离开。要求所有 构成 的一个排列,且 。
树是有效的,当且仅当所有车辆都能按计划进入和离开,即不存在一辆车在另一辆车经过其路径时仍停在某个顶点上。目标是:对所有具有 个顶点、 个叶子(根不算叶子)的带标号树,计算上述有效序列对 的总数,对 取模。
单棵树的分析
有效性条件
树有效当且仅当:对于任意顶点 ,其子树中所有顶点的 和 时间区间要么完全包含在 内,要么与 不交。
换句话说, 不能包含任何其子树外部顶点的端点。这等价于:子树中的 和 必须成对嵌套,且与外部完全分离。
计数公式
通过动态规划或组合分析可得,对于一棵固定的树,有效序列对的数量为
其中 是以 为根的子树大小(包含 )。
对所有树求和
我们需要计算
$$\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}. $$
转化为拓扑排序计数
已知一个经典结论:一棵树的所有拓扑排序(即满足父节点编号小于子节点的排列)的数量为
因此,
于是,
$$\sum_{T} \frac{1}{\prod s_i} = \frac{1}{n!} \sum_{T} (\text{拓扑排序数}). $$
交换求和顺序
固定一个拓扑排序(即 的一个排列 ,满足如果 是 的祖先则 ),计算有多少棵树以 为根且具有该拓扑排序。
这等价于:每个顶点 的父节点必须是拓扑序中比它小的某个顶点,且树恰好有 个叶子。
递推关系
设 表示 个顶点的树(根为 )的拓扑排序数之和(对所有树求和)。
考虑顶点 (最大标号)的位置:- 若 是叶子:则它必须挂在某个编号 的顶点下,且该顶点原本不是叶子(否则会增加叶子数)。
贡献为 。 - 若 不是叶子:则它可挂在任意 个叶子之一(因为叶子数不变)。
贡献为 。
因此:
边界条件:, for 。
与欧拉数的关系
该递推与欧拉数 (计数长度为 的排列中恰好有 个下降的个数)的递推一致:
比较可得:
其中 是欧拉数,通常定义为
$$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). $$由于 ,最终公式为
$$\boxed{\text{ans} = \frac{(2n)!}{n! \cdot 2^n} \cdot A(n-1, k-1)}. $$
欧拉数的计算
欧拉数 可通过容斥公式在 时间内计算:
$$A(n, m) = \sum_{j=0}^{m} (-1)^j \binom{n+1}{j} (m+1-j)^n. $$由于 ,但 可能很大,直接求和不可行。
但注意 ,且 总和不超过 ,我们可以在每次查询时用 计算,总复杂度可接受。
算法步骤
- 预处理阶乘和逆元到 。
- 对于每个测试用例 :
- 若 或 ,答案为 。
- 计算欧拉数 使用容斥公式。
- 计算 $\frac{(2n)!}{n! \cdot 2^n} \cdot A(n-1, k-1) \bmod 998244353$。
- 输出答案。
代码实现
#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
信息
- ID
- 6229
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者