[CEOI2007] 树的匹配 Treasury 题解

前言

题目链接:洛谷

题目简述

给一棵树,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配。

题目分析

先来考虑较简单的最大匹配数。对于某一个结点,它有以下三种状态:

  1. 不参与匹配;
  2. 和某一个儿子匹配;
  3. 和父亲匹配。

考虑使用树形 DP,我们发现,只需要知道一个孩子,和或不和自己匹配的最大匹配是多少,也即,在 DP 时,前两种状态在向上合并时是相同的——都不能和父亲构成一个新的匹配。那我们就可以记 \(f[i][0 / 1]\) 表示 \(i\) 这个点,能或不能和父亲匹配构成的最大匹配数。为了方便,我们把一个匹配的贡献记在孩子上。那么答案 \(ans = f[root][0]\)。接下来考虑递推式。

以下考虑对于一个结点 \(yzh\) 和其一个孩子 \(xym \in \operatorname{son}(yzh)\)

对于 \(f[yzh][1]\),即 \(yzh\) 可以和她父亲合并,那么她不能和她的孩子构成匹配。也就是只能从孩子的 \(f[xym][0]\) 转移而来。

\[f[yzh][1] = 1 + \sum f[xym][0] \]

对于 \(f[yzh][0]\),即 \(yzh\) 可以不参与匹配,或者和某一个孩子构成匹配,相应转移即可。

\[\begin{aligned} f[yzh][0] &= \max \Bigg \lbrace \sum f[xym][0], \sum f[xym][0] + \max \lbrace f[xym][1] - f[xym][0] \rbrace \Bigg \rbrace \\ &= \sum f[xym][0] + \max \lbrace f[xym][1] - f[xym][0] \rbrace \cup \lbrace 0 \rbrace \end{aligned} \]

对于方案数,我们再记 \(g[i][0 / 1]\),转移时把 \(\sum\) 变成 \(\prod\) 即可。

\[g[yzh][1] = \prod g[xym][0] \]

如果 \(f[yzh][0]\) 在不和任何一个儿子匹配时取得 \(\max\)

\[g[yzh][0] = \prod g[xym][0] \]

如果和某一个孩子 \(xym' \in \operatorname{son}(yzh)\) 匹配时取得 \(\max\)

\[g[yzh][0] = g[xym'][1] \prod _ {xym \neq xym'} g[xym][0] \]

注意,如果有多个 \(xym'\) 满足,应根据加法原理对 \(g[yzh][0]\) 累加;如果 \(f[xym'][1] = f[xym'][0]\),在不参与匹配的方案数也要累加进来。

最后注意到题目最后一句话:

其中 \(40\%\) 的数据答案不超过 \(10^8\)

我们要使用高精度存 \(g\) 的答案。有个 trick,\(\prod \limits _ {xym \neq xym'}\) 可以用前后缀积来优化。

代码

略去了高精度 BigInteger 的实现。

#include <vector>
#include <iostream>
#include <cstdio>
using namespace std;

int n;
vector<int> edge[1010];
BigInteger g[1010][2];
int f[1010][2];

void dfs(int now) {
	f[now][1] = 1, g[now][1] = 1;
	unsigned mx = 0, son = 0;
	for (unsigned i = 0; i < edge[now].size(); ++i) {
		int to = edge[now][i];
		++son, dfs(to);
		f[now][1] += f[to][0], g[now][1] *= g[to][0];
		if (f[to][1] - f[to][0] > f[edge[now][mx]][1] - f[edge[now][mx]][0])
			mx = i;
	}
	if (!son) return g[now][0] = 1, void();
	f[now][0] = f[now][1] - 1 + f[edge[now][mx]][1] - f[edge[now][mx]][0];
	static BigInteger suf[1010]; suf[edge[now].size()] = 1;
	for (unsigned i = edge[now].size() - 1; ~i; --i)
		suf[i] = suf[i + 1] * g[edge[now][i]][0];
	BigInteger pre = 1;
	for (unsigned i = 0; i < edge[now].size(); ++i) {
		int to = edge[now][i];
		if (f[to][1] - f[to][0] == f[edge[now][mx]][1] - f[edge[now][mx]][0])
			g[now][0] += pre * suf[i + 1] * g[to][1];
		pre *= g[edge[now][i]][0];
	}
	if (f[edge[now][mx]][1] - f[edge[now][mx]][0] == 0)
		g[now][0] += g[now][1];
}

signed main(){
	scanf("%d", &n);
	for (int i = 1, u, v, k; i <= n; ++i) {
		scanf("%d%d", &u, &k);
		while (k--) scanf("%d", &v), edge[u].push_back(v);
	}
	dfs(1);
	printf("%d\n", f[1][0]);
	printf("%s", string(g[1][0]).c_str());
	return 0;
}

后记 & 反思

一道很水的题目,但是模拟赛时没写出来,寄。

posted @ 2024-07-11 10:12  XuYueming  阅读(20)  评论(0编辑  收藏  举报