燔祭
提交地址
statement
计算满足如下条件的带标号有根树数量:
- 这棵树一共有 \(n\) 个节点。
- 每个节点都有一个整数权值,且在区间 \([1,m]\) 内。
- 每个节点的权值都不大于其父节点的权值。
答案可能很大,只需输出答案对 \(998244353\) 取模的值。
两棵树 \(T_1\) 和 \(T_2\) 不同当且仅当两棵树的节点数不同或者根节点不同或者存在一个编号 \(i\) 使得 \(T_1\) 和 \(T_2\) 中 \(i\) 号节点的父节点编号不同或者 \(i\) 号节点的权值不同。
sol1
\(m = 1\)
对有标号有根树计数。
Prufer
有标号无根树与长 \(n - 2\) 的序列一一对应,有标号有根树的数量就是 \(n ^ {n - 1}\)。
EGF
考虑有标号有根树的 EGF,假设是 \(F\)。
这里,我们认为 \(0\) 个点的树数量是 \(0\),即 \([x ^ 0]F = 0\)。
考虑拎出来一个根,那么 EGF 是 \(x\)。
然后考虑根的子树,这是一个集合划分的过程,而我们知道单独一个子树的 EGF 依然是 \(F\),所以:
注意 EGF 乘积是组合在一起,所以这是有根的,同时也是有标号的。特别的,\(\exp\) 中的常数项是 \(1\),圆了之前认为 \(F\) 的常数项是 \(0\)。
然后得到:\(F\exp(-F) = x\)。
令 \(G = x\exp(-x)\),则 \(G(F(x)) = x\),互为复合逆。
依据拉格朗日反演得:\([x ^ n]F(x) = \dfrac{n ^ {n - 1}}{n!}\)。
得到有标号有根树的数量是 \(n ^ {n - 1}\)。
\(m > 1\)
对带有堆限制的有标号有根树计数。
Prufer
思路是发现「已知权值序列对树的形态计数」比「已知树的形态对权值序列计数」好下手。
并且注意到一棵树权值种类是 \(O(n)\) 的,与 \(m\) 是无关的,任务就是求有 \(i\) 种权值的树的数量,带上 \(\binom{m}{i}\) 求和即可。
为了「已知权值序列对树的形态计数」,我们考虑枚举排序后的权值序列,为了记录权值种类可以分权值段 dp。
考虑当前权值序列长为 \(i\),有 \(c\) 种权值,最后一段相同权值的段长为 \(j\)。
该段可以向前面 \(i - j\) 随意连边,瓶颈在内部连边,考虑其内部其实连成了森林,假设有 \(k\) 个连通块。
加入虚点把森林看成根有度数限制的树,此时的转移系数是:
然后容易利用二项式定理去掉枚举 \(k\)。时间复杂度 \(O(n ^ 3)\)。
#include <bits/stdc++.h>
using namespace std;
constexpr int mod = 998244353;
int add(int a, int b) {
a += b;
if (a < mod) {
return a;
} else {
return a - mod;
}
}
int sub(int a, int b) {
a -= b;
if (a >= 0) {
return a;
} else {
return a + mod;
}
}
int mul(int a, int b) {
return 1ll * a * b % mod;
}
int qpow(int a, int b) {
int c = 1;
while (b) {
if (b & 1) {
c = mul(c, a);
}
a = mul(a, a);
b >>= 1;
}
return c;
}
int inv(int x) {
return qpow(x, mod - 2);
}
constexpr int N = 405;
int n, m, C[N][N], power[N][N], dp[N][N], down[N], fac[N];
int main() {
cin >> n >> m;
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = mul(fac[i - 1], i);
}
for (int i = 0; i <= n + 1; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
}
}
for (int i = 1; i <= n; i++) {
power[i][0] = 1;
for (int j = 1; j <= n; j++) {
power[i][j] = mul(power[i][j - 1], i);
}
}
down[0] = 1;
for (int i = 1; i <= min(n, m); i++) {
down[i] = mul(down[i - 1], m - i + 1);
}
for (int i = 1; i <= n; i++) {
dp[1][i] = mul(power[i][i - 1], C[n][i]);
}
for (int c = 2; c <= min(n, m); c++) {
for (int i = c; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[c][i] = add(dp[c][i], mul(dp[c - 1][i - j], mul(C[n - i + j][j], mul(i - j, power[i][j - 1]))));
}
}
}
int ans = 0, ifac = 1;
for (int i = 1; i <= min(n, m); i++) {
ifac = mul(ifac, inv(i));
ans = add(ans, mul(dp[i][n], mul(down[i], ifac)));
}
cout << ans << "\n";
return 0;
}
EGF
类似的我们考虑如何处理权值。
假设根节点权值是 \(k\) 的 EGF 是 \(F_k\)。子树的 EGF 是 \(\sum\limits_{i \le k} F_i\),假设是 \(G_k\)。
则:
对比 \([x^n]\) 得:
递推中需要同时递推 \(\exp\) 的值,可以暴力做到 \(O(n ^ 2m)\)。
关于已知 \(F\),求 \(\exp(F)\),利用计算导数得到递推性质。
假设 \(G = \exp(F)\),则 \(G' = F'\exp(F) = F'G\),对比 \([x^n]\) 即可 \(O(n ^ 2)\) 递推。
最后当 \(m\) 比较大时,臆测答案是关于 \(m\) 的 \(O(n)\) 次多项式,实际从 Prufer 做法可知,进行拉格朗日插值即可。
还没有实现该做法。