Codeforces 627D 题解
lzy20091001 · · 题解
Codeforces 627D Preorder Test
思路
“前
为了方便表述,对树中任意一个结点
-
将“以
u 为根的子树”简记为“子树u ”; -
若
a_u \ge \operatorname{goal} 则称u 为“黑点”,否则称u 为“白点”; -
若子树
u 内全是黑点,则称子树u 是“满的”,否则称其是“不满的”。
那么我们的任务就是构造一个 DFS 序使得“遇到第一个白点前能遇到尽可能多的黑点”。DFS 时会一棵子树一棵子树地遍历,我们可以依此得出重要性质:如果一棵树的根已经确定(即 DFS 的起点确定),在 DFS 时我们必定会先遍历所有满的子树,然后选一个最优的不满子树进行遍历,直到碰到这个子树的第一个白点。
容易发现这个问题具有最优子结构性质和无后效性——我们只需要知道每个子树中“遇到第一个白点前能遇到多少黑点”。于是树形 DP 的状态呼之欲出——设
有转移方程:
如果这么做的话需要枚举整棵树的根结点(即换根),总时间复杂度为
但是还有一种情况:如果
总时间复杂度为
实现
#include <iostream>
#include <vector>
using namespace std;
const int N = 2e5, A = 1e6;
int n, k, goal;
int a[N + 5], siz[N + 5], f[N + 5], g[N + 5];
vector<int> edge[N + 5];
void get_siz(int u, int fa) // 预处理 siz
{
siz[u] = 1;
for (auto v : edge[u])
if (v != fa)
{
get_siz(v, u);
siz[u] += siz[v];
}
}
void calc(int u, int fa) // 树形 DP
{
int max1 = 0, max2 = 0, maxg = 0; // 在所有不满子树中,最大的 f、次大的 f、最大的 g
for (auto v : edge[u])
if (v != fa)
calc(v, u);
if (a[u] >= goal) // 分类讨论,若 a[u] < goal 则显然 f[u] = g[u] = 0,在初始化中已实现
{
f[u] = 1; // u 自己是黑点
for (auto v : edge[u])
if (v != fa)
if (f[v] == siz[v]) // 子树 v 是满的
f[u] += f[v];
else // 子树 v 是不满的,维护 max1, max2, maxg
{
if (f[v] >= max1)
max2 = max1, max1 = f[v];
else
max2 = max(max2, f[v]);
maxg = max(maxg, g[v]);
}
// 求解 g[u](当前 f[u] 未加上不满子树的贡献)
if (max1 == 0) // 子树全满
g[u] = f[u];
else if (max2 == 0) // 只有 1 个不满子树
g[u] = f[u] + maxg;
else // 有不止 1 个不满子树
g[u] = f[u] + max1 + max2;
f[u] += max1; // 为 f[u] 加上不满子树的贡献
}
}
bool check(int val) // 二分中的 check()
{
int res = 0;
goal = val;
// 树形 DP 初始化
fill(f + 1, f + n + 1, 0);
fill(g + 1, g + n + 1, 0);
calc(1, 0);
for (int i = 1; i <= n; i++)
res = max(res, g[i]);
return res >= k;
}
int search() // 二分函数
{
int l = 1, r = A, res = -1;
while (l <= r)
{
int mid = (l + r) / 2;
if (check(mid))
l = mid + 1, res = mid;
else
r = mid - 1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int maxa = 0;
cin >> n >> k;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1, u, v; i < n; i++)
{
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
get_siz(1, 0);
cout << search() << "\n";
return 0;
}