CF708C Centroids

\(\text{题目描述}\)

对于每一个点判断能否通过一次截取树上的一棵子树拼到别的点上的方式把这个点变成重心

\(\text{思路}\)

  • 一个点如果本身就是重心不需要操作

  • 一个点不满足条件说明它的某一个子树大小超过了 \(\frac{n}{2}\)
    因为是无根树所以也有可能是父亲方向的所有点的大小超过了限制

  • 我们对超过了限制的部分截下来一段大小不超过 \(\frac{n}{2}\) 的子树拼到这个点下

  • 问题转换成了对每一个点判断能否对他超出了限制的子树截取下来大小满足限制的一个子树(子树的子树)

  • 不妨以 \(1\) 为根

  • 对于儿子方向,记录 \(f_{i,0}\) 表示大小不超过 \(\frac{n}{2}\) 的最大子树
    记录 \(f_{i,1}\) 表示大小不超过的次大子树

  • 对一个点,如果他的重儿子子树大小超过限制了,就判断
    \(siz_{son_u} - f_{son_u, 0} \leq \frac{n}{2}\),满足就说明儿子的限制可以被满足,\(u\)可以作为重心,否则不行

  • 也就是尝试在超过限制的儿子上截下来一段大小最大的符合条件的子树

  • 对于父亲方向,我们还要维护父亲方向大小不超过的 \(\frac{n}{2}\) 的最大子树 \(g_u\)

  • 对一个点他的父亲方向子树大小是 \(n - siz_u\) 记为 \(fsiz\)

  • 如果 \(fsiz - g_u \leq \frac{n}{2}\) 说明父亲方向的子树大小限制可以被满足

  • 也就是尝试在超过限制的父亲子树上截取一个大小最大的子树是的父亲方向满足限制

  • 关于 \(g_u\) 怎么求

  • 首先根的 \(g_1 = 0\), 因为没有父亲
  • 假设现在已知 \(g_u\) ,要求出 \(g_v\)
  • 那么 \(g_v\) 可能是 \(u\) 上截取的一段 也就是 \(f_{u,1}\)\(u\) 上截的最大一段,但是如果我们的 \(f_{u,1}\) 的这个最大值就是在 \(v\) 取到的,就不能把它算到答案,所以是次大值 \(f_{u,0}\)
  • 第二种情况 \(g_v\) 来源于 \(u\) 的父亲方向
  • 如果 \(fsiz_u\) 本身就在限制\(\frac{n}{2}\) 内,那么\(g_u = max(g_u, fsiz_u)\)
  • 否则就和 \(g_u\)\(max\)
  • 这表示如果能把 \(u\) 的父亲方向整棵子树截取下来那么就取
  • 否则就取 \(u\) 的父亲方向的最大一段子树

\(\text{code}\)

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N = 1e6 + 10;
int n, f[N][2], p[N], g[N], ans[N];
int siz[N], son[N];
vector<int> G[N];
void dfs(int u,int fat) {
    int val;
    siz[u] = 1;
    for(int v : G[u]) {
        if(v == fat) continue;
        dfs(v, u);
        siz[u] += siz[v];
        if(son[u] == 0 || siz[v] > siz[son[u]]) son[u] = v;
        if(siz[v] <= n / 2) val = siz[v];
        else val = f[v][0];
        if(f[u][0] < val) {
            f[u][1] = f[u][0];
            f[u][0] = val;
            p[u] = v;
        } else if(f[u][1] < val) {
            f[u][1] = val;
        }
    }
}
// 这里dfs1还记录了 p[u] 表示取到最大的f[u][1] 的是哪一个儿子
void dfs2(int u,int fat) {
    int val;
    ans[u] = 1;
    if(siz[son[u]] > n / 2) ans[u] = (siz[son[u]] - f[son[u]][0] <= n / 2);
    else if(n - siz[u] > n / 2) ans[u] = (n - siz[u] - g[u] <= n / 2); // 分别判断儿子方向和父亲方向是否可以满足限制
    // 注意n个点如果有超过n/2的子树,那么当然不会父亲和儿子的子树大小都超过限制
    // 所以这里只要一个超过限制并且可以通过修复满足限制
    // 那么就可以直接发现u是一个合法的答案点
    for(int v : G[u]) {
        if(v == fat) continue;
        if(n - siz[u] > n / 2) val = g[u];
        else val = n - siz[u];
        g[v] = max(g[v], val);
        if(p[u] == v) g[v] = max(g[v], f[u][1]);
        else g[v] = max(g[v], f[u][0]);
        dfs2(v, u);
    }
}
signed main(){
	ios::sync_with_stdio(false);
	// freopen(".in","r",stdin);
	// freopen(".out","w",stdout);
    cin >> n;
    fore(i, 2, n) {
        int u, v;
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    dfs2(1, 0);
    fore(i, 1, n) cout << ans[i] << ' ';
    cout << '\n';
    // cout << g[1] << '\n';
	return 0;
}


posted @ 2026-09-10 19:43  wmq2012  阅读(5)  评论(0)    收藏  举报