Codeforces 1146F. Leaf Partition(树形DP)

Codeforces 1146F. Leaf Partition

题目大意

  • 给出一棵大小为 N N N的有根树,把所有叶子节点分成若干集合,求使得每个集合分别连成的最小连通块之间互不相交的划分方案数。
  • N ≤ 1 0 5 N≤10^5 N105

题解

  • 一眼看到这题,想到了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;
}

哈哈哈哈哈哈哈哈哈哈
原文地址:https://www.cnblogs.com/LZA119/p/13910026.html