Codeforces 1517F Reunion

用黑点表示这个志愿者没有来,白点表示这个志愿者来了。

定义 \(B(u, r) = \{ v \mid \text{dis}(u, v) \le r \}\),也就是距离 \(u\) 不超过 \(r\) 的结点集合。

首先,我们枚举 \(r\),计算有多少种方法满足 \(ans \ge r\)

考虑 \(ans\) 的实际意义是什么?存在某一个结点 \(u\),对于任意一个黑点 \(v\),都有 \(\text{dis}(u, v) > ans\)

于是可以想到一种判定方法:如果 \(\left(\bigcup\limits_{u\text{ is black}} B(u, r) \right) = \{1, 2, \cdots, n\}\),那么 \(ans < r\),反之 \(ans \ge r\)

所以现在就是要找出不满足上面这个式子的方案数。可以想到一种 DP,用 \(f(u, depth, height)\) 表示 \(u\) 的子树,当前未被覆盖的结点最深为 \(depth\),以及这棵子树中最浅的黑点可以往上覆盖到的高度是 \(height\)。直接做的话是 \(O(n^3)\) 的。

可以发现一个优化:

  1. 如果当前子树中存在未被覆盖的结点 \(x\),那么 \(height\) 是不重要的。

这是因为,为了保证所有的点都被覆盖到,\(u\) 子树外必然存在另一个黑点 \(y\) 满足 \(\text{dis}(x, y) \le r\),那么就可以得知,「\(u\) 子树中最浅的黑点所能往上覆盖到的点集」是「\(y\) 所能覆盖到的点集」的子集,自然就不必考虑。

  1. 如果当前子树中不存在未被覆盖的结点 \(x\),那么 \(depth\) 是不重要的。

\(depth\) 的含义就是未被覆盖的结点的最大深度,既然没有这样的点,那自然不重要。

于是,我们得出了一个结论:\(depth / height\) 只会有一者是有意义的

于是更改我们的状态,用 \(f(u, i)\) 表示在 \(u\) 的子树中:

  • \(i \ge 0\),表示最浅的黑点能往上额外延伸 \(i\) 的距离。
  • \(i < 0\),表示最深的没被覆盖的点离 \(u\) 的距离为 \(-i-1\)

每个点 \(u\)\(f(u, i) \ne 0\)\(i\)\(O(\text{size}(u))\) 的,所以单次 DP 时间复杂度为 \(O(n^2)\)。转移的写法和树形背包类似,注意复杂度不要写假掉。

总时间复杂度 \(O(n^3)\)

#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define delta (n + 1)
using namespace std;
typedef long long ll;
const int N = 305, M = 605, MOD = 998244353, INV2 = 499122177;
int n, m, r, f[N][M], ans;
void Add(int &x, int y) { if((x += y) > MOD) x -= MOD; }
vector<int> G[N];
vector<pair<int, int>> tmp1, tmp2;
void Dp(int u, int fa)
{
	fill(f[u], f[u] + m, 0);
	int son = 0;
	for(int v : G[u])
	{
		if(v == fa) continue;
		Dp(v, u); son++;
	}
	f[u][r + delta] = f[u][-1 + delta] = 1;
	if(!son) return;
	for(int v : G[u])
	{
		if(v == fa) continue;
		tmp1.clear(); tmp2.clear();
		for(int i = 0; i < m; i++) if(f[u][i]) { tmp1.pb(mp(i - delta, f[u][i])); f[u][i] = 0; }
		for(int i = 0; i < m; i++) if(f[v][i]) tmp2.pb(mp(i - delta, f[v][i]));
		for(pair<int, int> t1 : tmp1) for(pair<int, int> t2 : tmp2)
		{
			int i = t1.first, j = t2.first, w = (ll)t1.second * t2.second % MOD;
			if(i < 0 && j < 0) Add(f[u][min(i, j - 1) + delta], w);
			if(i < 0 && j >= 0) Add(f[u][(i + j >= 0 ? j - 1 : i) + delta], w);
			if(i >= 0 && j < 0) Add(f[u][(i + j >= 0 ? i : j - 1) + delta], w);
			if(i >= 0 && j >= 0) Add(f[u][max(i, j - 1) + delta], w);
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin >> n; m = n * 2 + 5;
	for(int i = 1; i < n; i++)
	{
		int u, v; cin >> u >> v;
		G[u].pb(v); G[v].pb(u);
	}
	for(r = 1; r < n; r++)
	{
		Dp(1, 0);
		for(int i = 0; i < delta; i++) Add(ans, f[1][i]);
	}
	for(int i = 1; i <= n; i++) ans = (ll)ans * INV2 % MOD;
	printf("%d\n", ans);
	return 0;
}
posted @ 2021-05-25 22:25  syksykCCC  阅读(69)  评论(0编辑  收藏  举报