换根DP例题2
给你一棵 n n n 个结点的无向树,你可以执行最多一次的边替换操作,即从树中删除任意一条边,但不删除顶点,然后再往树内加一条边,且不添加新结点,操作完后该图必须是树。
对于每个结点,你需要判断是否能通过执行至多一次操作,使得它们变成树的重心。
树的重心指的是,如果我们把树的重心从树中移除,那么分裂出的连通分量的大小必须不超过 n 2 \frac{n}{2} 2n。
数据范围: 2 ≤ n ≤ 4 × 1 0 5 2\le n\le 4\times10^5 2≤n≤4×105。
题解
思绪
这题需要对每个结点都判断是否至多一次操作后能成为重心,这个特征使得我们可以往换根 DP 上思考。
接下来思考操作的最优性,若树根不是重心,只需从一棵大小大于 n 2 \frac{n}{2} 2n 的子树内找到一棵大小为 x x x 的子树,将其摘出并连接到根上即可。
设那个大于 n 2 \frac{n}{2} 2n 的子树大小为 s s s,那么 x x x 应该满足 { s − x ≤ n 2 x ≤ n 2 \begin{cases}s-x\le\frac{n}{2}\\ x\le \frac{n}{2}\end{cases} {s−x≤2nx≤2n,整理得 s − n 2 < x < n 2 s-\frac{n}{2}<x<\frac{n}{2} s−2n<x<2n,于是得到了关于每次操作所摘子树的约束。
接下来应该思考,一棵树的答案如何组成?再细致一点就是,那个大小大于 n 2 \frac{n}{2} 2n 的子树的根是谁?如何取得 x x x?
任意选一个结点 u u u 作为根,设其儿子为 v 1 , v 2 , ⋯ , v k v_1,v_2,\cdots,v_k v1,v2,⋯,vk,设 s i z e u size_u sizeu 表示以 u u u 为根的子树大小。
-
如果 s i z e v 1 , s i z e v 2 , ⋯ , s i z e v k size_{v_1},size_{v_2},\cdots,size_{v_k} sizev1,sizev2,⋯,sizevk 均不超过 n 2 \frac{n}{2} 2n,那么 u u u 需要 0 0 0 次操作后会变为重心。
-
如果 s i z e v i > n 2 size_{v_i}>\frac{n}{2} sizevi>2n,那么要想让 u u u 变成重心,只能去操作 v i v_i vi 子树内的点。
我们希望操作完之后 v i v_i vi 尽量小, x x x 尽量大,所以我们只需要选择 v i v_i vi 内的一个最大的且不大于 n 2 \frac{n}{2} 2n 的子树删去即可。如果删去之后 v i v_i vi 的子树大小仍大于 n 2 \frac{n}{2} 2n,那么说明该点经过一次操作无法成为重心。
如果以其他结点为根会比较复杂,我们可以令重心 G G G 为根,然后如何找某个子树里的最大且不超过 n 2 \frac{n}{2} 2n 的子树呢,可以参考下面这个过程:
不妨假设当前结点是
u
u
u,我们有一个 set<PII> 存的是不包含当前
u
u
u 的子树,但包含
u
u
u 的父亲方向的子树内的所有小于
n
2
\frac{n}{2}
2n 的子树大小。
- 计算当前结点答案,因为当前结点子树必然小于
n
2
\frac{n}{2}
2n,所以我们需要从
set里找一个最大值,然后看一下 n − s i z e u − max n-size_u-\max n−sizeu−max 是否大于 n 2 \frac{n}{2} 2n。 - 为下次 DFS 做准备,把当前结点向下的所有子树的 { s i z e v , v } \{size_v,v\} {sizev,v} 塞入 s e t set set 里面,因为子树必然大小都不超过 n 2 \frac{n}{2} 2n,所以直接塞入即可。
- 开始 DFS,当遍历到
v
v
v 时,我们先从
set中删去 { s i z e v , v } \{size_v,v\} {sizev,v},**如果以 f a v fa_v fav 为根的子树大小( n − s i z e v n-size_v n−sizev)不超过 n 2 \frac{n}{2} 2n 的话,我们也需要将这个子树大小插入 s e t set set 里。**此时的set更新完毕,所以直接进行 DFS(v, u)。
代码
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18
void slove () {
int n;
cin >> n;
vector <int> g[n + 1];
for (int i = 1; i <= n - 1; i ++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int G = 0;
vector <int> siz(n + 1, 0);
function <void (int, int)> dfs =[&] (int u, int fa) {
siz[u] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs (v, u);
siz[u] += siz[v];
}
};
dfs(1, 0);
for (int i = 1; i <= n; i++) {
bool found = true;
if (n - siz[i] > n/2) continue;
for (auto v : g[i]) {
if (siz[v] > siz[i]) continue;
if (siz[v] > n/2) found = false;
}
if (found) G = i;
}
dfs (G, 0);
vector <int> ans (n + 1, 0);
set <PII> t;
function <void(int, int)> dfs2 =[&] (int u, int fa) {
bool found = true;
int maxn = 0;
for (auto i = t.rbegin(); i != t.rend() ;i--) {
if (i->second != u) {
if (n - i->first - siz[u] > n/2) found = false;
break;
}
}
if (found) ans[u] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
t.insert(PII{siz[v], v});
}
for (auto v : g[u]) {
if (v == fa) continue;
t.erase(PII{siz[v], v});
if (n - siz[v] <= n/2) t.insert(PII{n - siz[v], u});
dfs2 (v, u);
t.insert(PII{siz[v], v});
if (n - siz[v] <= n/2) t.erase(PII{n - siz[v], u});
}
};
dfs2 (G, 0);
for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
cout << endl;
}
signed main () {
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
slove();
}
代码
9276

被折叠的 条评论
为什么被折叠?



