CF708C Centroids [树形DP,换根DP]
Description
给定一棵树。
至多进行一次操作:删去一条边,连接一条新边,保证操作完后仍是树。
问每个点在进行操作后是否可以成为树的重心。
Solution
性质\(1\):若一个点不是树的重心,则它的必然有一个大小大于 \(\lfloor n/2\rfloor\) 的子树。
性质\(2\):如果一个点合法,要么它本来就是重心,要么它只有一个子树大于 \(\lfloor n/2\rfloor\),且从这个子树中移除一个最大的小于等于 \(\lfloor n/2\rfloor\) 的子树可以使它自己也小于等于 \(\lfloor n/2\rfloor\)。
考虑 树形dp,设 \(f(u)\) 表示以 \(u\) 为根的子树中,最大的不超过 \(\lfloor n/2\rfloor\) 的子树大小,初始先以 \(1\) 为总根,转移如下,
当 \(size_v\le \lfloor n/2\rfloor\) 时,\(f(u) = \max(f(u),size_v)\),
否则 \(f(u) = \max(f(u),f(v))\),
\(v\in son_u\)。
考虑如何处理子树外的答案,即换根,设 \(g(u)\) 表示不在以 \(u\) 为根的子树内的答案,
为了方便转移,我们需要更改一下状态 \(f\),设 \(f(u,0/1)\) 分别表示最大答案和次大答案,转移和原来类似,然后还要记录一下每个点的最佳转移状态,就是它最终是由哪个点转移过来的,
\(g\) 的转移如下,
当 \(f(u,0)\) 的最佳转移是 \(v\) 时,
- 如果 \(n-size_u\le \lfloor n/2\rfloor\),那么 \(g(v) = \max(g(u),n-size_u,f(u,1))\),
- 否则 \(g(v) = \max(g(u),f(u,1))\),
否则,
- 如果 \(n-size_u\le \lfloor n/2\rfloor\),那么 \(g(v) = \max(g(u),n-size_u,f(u,0))\),
- 否则 \(g(v) = \max(g(u),f(u,0))\)。
最终根据性质\(2\),判断每个点是否合法即可。
Code
const int N = 4e5 + 5;
int n;
vector <int> e[N];
int siz[N], f[N][2], mx[N]; // f(u) : 以 u 为根的子树内满足大小 <=n/2 的最大和次大子树大小 (整棵树以 1 为根)
// mx(u) : 记录 f(u) 的最佳转移 v
int g[N];
bool ans[N];
void dfsF(int u, int fa){
siz[u] = 1;
for(int v : e[u]){
if(v == fa) continue;
dfsF(v, u);
siz[u] += siz[v];
if(siz[v] <= n / 2){
if(siz[v] > f[u][0]){ // 最大和次大不能从同一个子树转移!
f[u][1] = f[u][0];
f[u][0] = siz[v];
mx[u] = v;
}else if(siz[v] > f[u][1]) f[u][1] = siz[v];
}
else{
if(f[v][0] > f[u][0]){
f[u][1] = f[u][0];
f[u][0] = f[v][0];
mx[u] = v;
}else if(f[v][0] > f[u][1]) f[u][1] = f[v][0];
}
}
}
void dfsG(int u, int fa){
for(int v : e[u]){
if(v == fa) continue;
if(mx[u] == v){
if(n - siz[u] <= n / 2) g[v] = max(f[u][1], n - siz[u]);
else g[v] = max(f[u][1], g[u]);
}else{
if(n - siz[u] <= n / 2) g[v] = max(f[u][0], n - siz[u]);
else g[v] = max(f[u][0], g[u]);
}
dfsG(v, u);
}
}
void dfsAns(int u, int fa){
int mx = n - siz[u];
int cnt = 0, id = -1;
if(mx > n / 2) id = fa, cnt++;
for(int v : e[u]){
if(v == fa) continue;
if(siz[v] > n / 2){
mx = siz[v];
id = v;
cnt++;
}
dfsAns(v, u);
}
if(cnt == 0) ans[u] = true;
else if(cnt == 1 && id == fa && mx - g[u] <= n / 2) ans[u] = true;
else if(cnt == 1 && id != fa && mx - f[id][0] <= n / 2) ans[u] = true;
else ans[u] = false;
}
void Solve(){
cin >> n;
int u, v;
for(int i = 1; i <= n - 1; i++){
cin >> u >> v;
e[u].ps(v);
e[v].ps(u);
}
dfsF(1, 0);
dfsG(1, 0);
dfsAns(1, 0);
for(int i = 1; i <= n; i++) cout << ans[i] << ' ';
}