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)\) 的。
可以发现一个优化:
- 如果当前子树中存在未被覆盖的结点 \(x\),那么 \(height\) 是不重要的。
这是因为,为了保证所有的点都被覆盖到,\(u\) 子树外必然存在另一个黑点 \(y\) 满足 \(\text{dis}(x, y) \le r\),那么就可以得知,「\(u\) 子树中最浅的黑点所能往上覆盖到的点集」是「\(y\) 所能覆盖到的点集」的子集,自然就不必考虑。
- 如果当前子树中不存在未被覆盖的结点 \(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;
}