CF2122G Tree Parking 题解

Description

你得到一棵以 1 为根的树,共有 nn 个结点。

对于每个 1in1 \leq i \leq n,有一辆车会在时间 lil_i 从根结点进入。这辆车会沿着从根到结点 ii 的唯一路径(简单路径)瞬间行驶,并停在结点 ii。它将在时间 rir_i 沿原路返回离开。

在车辆停在某个结点期间,该结点将被占用,不能被其他车辆通过。

当所有车辆都能在它们想要的时间进入并离开时,我们称这棵树是合法的(valid)

请你计算有多少对序列 llrr 满足以下条件:

  • 对于每个 ii,都有 li<ril_i < r_i
  • llrr 拼接后得到的长度为 2n2n 的序列是 1,2,...,2n1, 2, ..., 2n 的一个排列;
  • 树是合法的(即所有车辆在各自的时间段可以顺利通过)。

现在的目标是:

对于所有nn 个结点、恰好有 kk 个叶子结点有标号树(labeled trees)(注意:根结点 1 不算作叶子),求上述合法方案数的总和

由于答案可能很大,请输出其对 998244353998244353 取模后的结果。

1k<n2×1051\leq k<n\leq 2\times 10^5

Solution

首先考虑如果已知树的形态之后怎么求答案。

显然是按照子树从下往上考虑,假设已经知道了 uu 子树内除去 uullrr 分配方案了,由于 [lu,ru][l_u,r_u] 不能包含任意一个子树内点的左右端点,则 lu,rul_u,r_u 只有 2szu12\cdot sz_u-1 种方案,总方案数为 (2n)!12szu=(2n)!2n1szu\displaystyle(2n)!\cdot\prod\frac{1}{2sz_u}=\frac{(2n)!}{2^n}\cdot\prod\frac{1}{sz_u}

现在只需要求出所有有标号有根树的 1szu\displaystyle\prod\frac{1}{sz_u} 即可,将这个乘上 n!n! 就是一个有根树的拓扑序数量。

考虑先固定拓扑序,再计算可以以当前序列为拓扑序的方案数。

即设 f(n,k)f(n,k) 表示已经确定了拓扑序 nn 个数的连边方案,有 kk 个叶子的方案数,则转移为:f(n,k)=f(n1,k1)(nk)+f(n1,k)kf(n,k)=f(n-1,k-1)\cdot(n-k)+f(n-1,k)\cdot k。最终答案为 f(n,k)(n1)!f(n,k)\cdot(n-1)!,因为拓扑序第一项必须为 11,其余的可以随便排列。

现在考虑怎么快速求 f(n,k)f(n,k)

注意到这个式子很像欧拉数 A(n,k)A(n,k) 的转移方程,即 A(n,k)=A(n1,k1)(nk+1)+A(n1,k)kA(n,k)=A(n-1,k-1)\cdot(n-k+1)+A(n-1,k)\cdot k,打一个表会发现 f(n,k)=A(n1,k)f(n,k)=A(n-1,k),再套用 A(n,k)=i=0k1(1)i(n+1i)(ki)n\displaystyle A(n,k)=\sum_{i=0}^{k-1}{(-1)^i\binom{n+1}{i}(k-i)^n} 即可。

最终的答案为 (2n)!2nnf(n,k)\displaystyle\frac{(2n)!}{2^n\cdot n}\cdot f(n,k)

时间复杂度:O(nlogn)O(n\log n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 4e5 + 5, kMod = 998244353;

int n, k;
int fac[kMaxN], ifac[kMaxN];

int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }
inline int getop(int x) { return (~x & 1) ? 1 : (kMod - 1); }

int C(int m, int n) {
if (m < n || m < 0 || n < 0) return 0;
return 1ll * fac[m] * ifac[n] % kMod * ifac[m - n] % kMod;
}

void prework(int n = 4e5) {
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = 1ll * i * fac[i - 1] % kMod;
ifac[n] = qpow(fac[n]);
for (int i = n; i; --i) ifac[i - 1] = 1ll * i * ifac[i] % kMod;
}

int calc(int n, int k) {
int ret = 0;
for (int i = 0; i < k; ++i)
inc(ret, 1ll * getop(i) * C(n + 1, i) % kMod * qpow(k - i, n) % kMod);
return ret;
}

void dickdreamer() {
std::cin >> n >> k;
std::cout << 1ll * fac[2 * n] * qpow(1ll * qpow(2, n) * n % kMod) % kMod * calc(n - 1, k) % kMod << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T;
prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

CF2122G Tree Parking 题解
https://sobaliuziao.github.io/2025/07/22/post/705fd313.html
作者
Egg_laying_master
发布于
2025年7月22日
许可协议