题解:P7118 Galgame
under_the_time · · 题解
题意
规定两棵无标号二叉树(可以为空)比较大小的方式如下:
- 相同形态的树大小相同。
- 如果节点个数不同,点数少的更小。
- 否则左子树不同时按照左子树比大小。
- 否则按照右子树比大小。
给定一棵
n 个节点的二叉树,求有多少棵无标号非空二叉树严格小于它。答案对998244353 取模。n\le10^6 。
思路
显然所有节点数
令原树中
但这样复杂度是
这里额外补充一下卡特兰数,即
我们考虑一个模型:从
其中蓝线为分界线,绿线即为一条合法路径。若不考虑限制,那么总方案数相当于在一共
对于图中红色的线即为一条不合法的路径。我们现在令作一条过
它的递推公式为
我们再考虑一个模型,也是最常用的模型:
-
- 有一个空栈,进栈
1,2,\cdots,n ,求出栈序列的个数。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5, P = 998244353;
int fac[maxn << 1], inv[maxn << 1], n;
int qp(int x, int y) {
int res = 1;
for (; y; y >>= 1, x = (1ll * x * x) % P)
if (y & 1) res = (1ll * res * x) % P;
return res;
}
void init() {
fac[0] = 1, inv[0] = 1;
for (int i = 1; i <= (n << 1); i ++)
fac[i] = (1ll * fac[i - 1] * i) % P, inv[i] = qp(fac[i], P - 2);
}
int C(int x, int y) {
if (x < y) return 0;
int p = (1ll * fac[x] * inv[y]) % P;
return (1ll * p * inv[x - y]) % P;
}
int Cat(int x) {
if (x == 0) return 1;
return ((C(x << 1, x) - C(x << 1, x - 1)) % P + P) % P;
}
int siz[maxn], son[maxn][2];
void dfs1(int u) { // 求出子树大小
if (!u) return ;
dfs1(son[u][0]), dfs1(son[u][1]);
siz[u] = 1 + siz[son[u][0]] + siz[son[u][1]];
}
int ans = 0;
void dfs2(int u, int k) { // 统计答案,其中 k 为递归中的系数
if (!u) return ;
if (siz[son[u][0]] <= siz[son[u][1]] + 1) // 按照子树大小使用公式计算
for (int i = 0; i < siz[son[u][0]]; i ++)
ans = (ans + (1ll * ((1ll * k * Cat(i)) % P) * Cat(siz[u] - i - 1)) % P) % P;
else {
int tmp = Cat(siz[u]);
for (int i = 0; i <= siz[son[u][1]]; i ++)
tmp = ((tmp - (1ll * Cat(i) * Cat(siz[u] - i - 1)) % P) % P + P) % P;
// cout << tmp << '\n';
ans = (ans + (1ll * k * tmp) % P) % P;
}
// cout << u << ' ' << ans << ' ' << k << '\n';
dfs2(son[u][0], (1ll * k * Cat(siz[son[u][1]])) % P), dfs2(son[u][1], k);
}
int main() {
scanf("%d", &n); init();
for (int i = 1; i <= n; i ++)
scanf("%d %d", &son[i][0], &son[i][1]);
int self = 0;
for (int i = 1; i < n; i ++) // 注意原题要求非空二叉树,所以答案不能统计空树的情况,故从 i=1 开始枚举。
self = (1ll * self + Cat(i)) % P;
dfs1(1), dfs2(1, 1); printf("%d", (1ll * self + 1ll * ans) % P);
return 0;
}