Codeforces 1146F. Leaf Partition
题目大意
- 给出一棵大小为
N
N
N的有根树,把所有叶子节点分成若干集合,求使得每个集合分别连成的最小连通块之间互不相交的划分方案数。
-
N
≤
1
0
5
N≤10^5
N≤105
题解
- 一眼看到这题,想到了LCA,想到了DFS序……
- 其实正解完全没这么复杂,只是简单的树形DP。
- 设
f
[
x
]
[
0
/
1
]
f[x][0/1]
f[x][0/1]表示节点
x
x
x向上延伸或不延伸的方案数(延伸是为了和其他子树内的叶子节点构成连通块),
- 乍一看它的儿子节点是否延伸对它是没有影响的,每个点的方案数就是所有儿子方案数的乘积,但事实上不是,
- 若该节点不延伸的话,它就不能只有一个儿子向上延伸,因为没有其他儿子和它匹配,但也可以所有儿子都没有向上延伸;
- 若该节点延伸的话,它就不能没有儿子向上延伸,因为它至少需要带上一个儿子继续去找其他子树匹配。
- 所以状态转移方程就很好写了,转移过程中会涉及到除法,直接用费马小定理求逆元即可。
代码
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200010
#define md 998244353
#define ll long long
int to[N], nxt[N], last[N], len = 0;
ll f[N][2];
void add(int x, int y) {
to[++len] = y;
nxt[len] = last[x];
last[x] = len;
}
ll ksm(ll x, ll y) {
if(!y) return 1;
ll l = ksm(x, y / 2);
if(y % 2) return l * l % md * x % md;
return l * l % md;
}
void dfs(int k) {
ll s = 1, s0 = 1;
for(int i = last[k]; i; i = nxt[i]) {
int x = to[i];
dfs(x);
s = s * (f[x][0] + f[x][1]) % md;
s0 = s0 * f[x][0] % md;
}
ll s1 = 0;
for(int i = last[k]; i; i = nxt[i]) {
int x = to[i];
s1 = (s1 + s0 * ksm(f[x][0], md - 2) % md * f[x][1]) % md;
}
f[k][0] = (s - s1 + md) % md;
f[k][1] = (s - s0 + md) % md;
if(!last[k]) f[k][1] = f[k][0] = 1;
}
int main() {
int n, i, x;
scanf("%d", &n);
for(i = 2; i <= n; i++) {
scanf("%d", &x);
add(x, i);
}
dfs(1);
printf("%lld
", f[1][0]);
return 0;
}