CF708C:Centroids(树形dp & 重心构造判断)

探讨如何通过最多一次边替换使树中每个顶点成为重心的方法。介绍了一种算法,该算法通过递归地分析每个节点及其子树的特性来确定是否可以通过更改边来实现目标。

C. Centroids
time limit per test
4 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

Tree is a connected acyclic graph. Suppose you are given a tree consisting of n vertices. The vertex of this tree is called centroid if the size of each connected component that appears if this vertex is removed from the tree doesn't exceed .

You are given a tree of size n and can perform no more than one edge replacement. Edge replacement is the operation of removing one edge from the tree (without deleting incident vertices) and inserting one new edge (without adding new vertices) in such a way that the graph remains a tree. For each vertex you have to determine if it's possible to make it centroid by performing no more than one edge replacement.

Input

The first line of the input contains an integer n (2 ≤ n ≤ 400 000) — the number of vertices in the tree. Each of the next n - 1 lines contains a pair of vertex indices ui and vi (1 ≤ ui, vi ≤ n) — endpoints of the corresponding edge.

Output

Print n integers. The i-th of them should be equal to 1 if the i-th vertex can be made centroid by replacing no more than one edge, and should be equal to 0 otherwise.

Examples
input
3
1 2
2 3
output
1 1 1 
input
5
1 2
1 3
1 4
1 5
output
1 0 0 0 0 
Note

In the first sample each vertex can be made a centroid. For example, in order to turn vertex 1 to centroid one have to replace the edge (2, 3) with the edge (1, 3).


题意:一棵树,判断每个节点是否能够通过操作:(删去一条边变成两棵树,然后将其中一棵并到另一棵的任意节点上) 变成树的重心。

思路:如果一个点的某个儿子或者祖先节点数大于n/2,那么就要执行操作,找到该枝能移除的最大的子树X(<=n/2),该枝节点总数-X<=n/2就是可以,否则不可以。向上向下维护一个节点能删去的最大子树。

//reference:YxuanwKeith
# include <bits/stdc++.h>
# define pb push_back
using namespace std;
const int maxn = 4e5+30;
vector<int>v[maxn];
int n, sons[maxn], mx_son[maxn], down[maxn], up[maxn];
void dfs(int cur, int pre)
{
    sons[cur] = 1;
    for(auto to : v[cur])
    {
        if(to == pre) continue;
        dfs(to, cur);
        sons[cur] += sons[to];
        down[cur] = max(down[cur], (sons[to]>n/2)?down[to]:sons[to]);
        mx_son[cur] = max(mx_son[cur], sons[to]);
    }
}

void dfs2(int cur, int pre)
{
    multiset<int>s;
    for(auto to : v[cur])
    {
        if(to == pre) continue;
        s.insert((sons[to]>n/2)?down[to]:sons[to]);
    }
    for(auto to : v[cur])
    {
        if(to == pre) continue;
        if(n-sons[to] <= n/2) up[to] = max(up[to], n-sons[to]);
        else
        {
            up[to] = max(up[to], up[cur]);
            s.erase(s.find((sons[to]>n/2)?down[to]:sons[to]));
            if(!s.empty()) up[to] = max(up[to], *s.rbegin());
            s.insert((sons[to]>n/2)?down[to]:sons[to]);
        }
        dfs2(to, cur);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<n; ++i)
    {
        int a, b;
        scanf("%d%d",&a,&b);
        v[a].pb(b), v[b].pb(a);
    }
    dfs(1, 0);
    dfs2(1, 0);
    for(int i=1; i<=n; ++i)
    {
        int ans = 1;
        if(mx_son[i] > n/2) ans = mx_son[i]-down[i] <= n/2;
        if(n - sons[i] > n/2) ans = n-sons[i]-up[i] <= n/2;
        printf("%d ",ans);
    }
    return 0;
}




### 计算和判断树的重心 在树的数据结构中,树的重心是指这样一个节点:当以该节点为根时,删除此节点后形成的任意子树的最大节点数最少。寻找树的重心是一个经典问题,在许多算法场景中有广泛应用。 #### 1. 树的重心定义 树的重心是满足以下条件的一个或多个节点: 对于某个节点 \( u \),以其为根划分整棵树为若干子树,则这些子树中最大子树的节点数量应尽可能小。形式化表达如下: \[ \text{max\_size}(u) = \max(\text{size of all subtrees after removing } u), \] 其中,\( \text{max\_size}(u) \leq \lceil n / 2 \rceil \)[^3]。 --- #### 2. 算法描述 ##### 输入 给定一棵有 \( n \) 个节点的无向连通树,边权可忽略(即只考虑拓扑结构)。 ##### 输出 找到树的一到两个重心节点。 ##### 步骤 1. **预处理子树大小** 首先通过深度优先搜索(DFS),计算每个节点的子树大小 \( \text{subtree}[u] \),表示以节点 \( u \) 为根的子树所含节点的数量。初始状态设定为 \( \text{subtree}[u] = 1 \) (仅包含自己)。 DFS 过程中递推关系为: \[ \text{subtree}[u] = 1 + \sum_{v \in \text{children}(u)}{\text{subtree}[v]} \] 2. **验证候选节点是否为重心** 在第二次遍历过程中,检查每个节点 \( u \) 是否满足重心条件。具体来说,对于每个节点 \( u \),需要分别考察两种情况下的子树规模: - 子树内部的最大子树规模; - 删除当前节点后剩余部分的规模(即父节点所在的部分)。 如果两者均不超过 \( \lfloor n / 2 \rfloor \),则 \( u \) 是一个重心。 3. **优化与边界条件** 若树有两个重心,则它们必定相邻[^3]。因此可以在第一次遍历时记录符合条件的第一个重心,并继续探索是否存在第二个重心。 --- #### 3. 实现代码 以下是基于 Python 的实现代码: ```python def find_centroid(n, edges): from collections import defaultdict tree = defaultdict(list) # 构建邻接表 for a, b in edges: tree[a].append(b) tree[b].append(a) subtree_size = [0] * (n + 1) centroid_found = False centroids = [] def dfs_subtree(u, parent): nonlocal subtree_size size = 1 for v in tree[u]: if v != parent: size += dfs_subtree(v, u) subtree_size[u] = size return size def dfs_find_centroid(u, parent, total): is_centroid = True max_subtree_size = 0 for v in tree[u]: if v != parent: if not dfs_find_centroid(v, u, total): # Recurse first to check children is_centroid = False max_subtree_size = max(max_subtree_size, subtree_size[v]) remaining_part = total - subtree_size[u] if remaining_part &gt; max_subtree_size: max_subtree_size = remaining_part if max_subtree_size &lt;= total // 2 and is_centroid: centroids.append(u) return False # Stop further recursion once we found the centroid(s) return True root = 1 dfs_subtree(root, -1) dfs_find_centroid(root, -1, n) return centroids # 测试样例 edges = [(1, 2), (1, 3), (1, 4), (4, 5)] centroids = find_centroid(5, edges) print(&quot;Centroids:&quot;, centroids) ``` --- #### 4. 复杂度分析 - **时间复杂度**: 整体算法分为两次 DFS 遍历,每次均为 \( O(n) \),故总时间为 \( O(n) \)[^3]。 - **空间复杂度**: 主要是存储邻接表和辅助数组的空间消耗,同样为 \( O(n) \). --- ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值