Remove Exactly Two ( [Codeforces Round 1000 (Div. 2)](httpsmirror.codeforces.comcontest2063) )

Remove Exactly Two ( Codeforces Round 1000 (Div. 2) )

Recently, Little John got a tree from his aunt to decorate his house. But as it seems, just one tree is not enough to decorate the entire house. Little John has an idea. Maybe he can remove a few vertices from the tree. That will turn it into more trees! Right?

You are given a tree ∗ ^{\text{∗}} of n n n vertices. You must perform the following operation exactly twice.

  • Select a vertex v v v;
  • Remove all edges incident to v v v, and also the vertex v v v.

Please find the maximum number of connected components after performing the operation exactly twice.

Two vertices x x x and y y y are in the same connected component if and only if there exists a path from x x x to y y y. For clarity, note that the graph with 0 0 0 vertices has 0 0 0 connected components by definition. † ^{\text{†}}

∗ ^{\text{∗}} A tree is a connected graph without cycles.

† ^{\text{†}} But is such a graph connected?

Input

Each test contains multiple test cases. The first line contains the number of test cases t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1t104). The description of the test cases follows.

The first line of each test case contains a single integer n n n ( 2 ≤ n ≤ 2 ⋅ 1 0 5 2 \le n \le 2 \cdot 10^5 2n2105).

Each of the next n − 1 n-1 n1 lines contains two integers u i u_i ui and v i v_i vi, denoting the two vertices connected by an edge ( 1 ≤ u i , v i ≤ n 1 \le u_i,v_i \le n 1ui,vin, u i ≠ v i u_i \neq v_i ui=vi). It is guaranteed that the given edges form a tree.

It is guaranteed that the sum of n n n over all test cases does not exceed 2 ⋅ 1 0 5 2 \cdot 10^5 2105.

Output

For each test case, output the maximum number of connected components on a separate line.

Example

Input

3
2
1 2
4
1 2
2 3
2 4
7
1 2
1 3
2 4
4 5
5 6
5 7

Output

0
2
4

Note

On the first test case, removing a vertex twice will make the graph empty. By definition, the number of connected components in the graph with 0 0 0 vertices is 0 0 0. Therefore, the answer is 0 0 0.

On the second test case, removing two vertices 1 1 1 and 2 2 2 leaves 2 2 2 connected components. As it is impossible to make 3 3 3 connected components with 2 2 2 vertices, the answer is 2 2 2.

On the third test case, removing two vertices 1 1 1 and 5 5 5 leaves 4 4 4 connected components, which are { 2 , 4 } \left\{ 2,4\right\} {2,4}, { 3 } \left\{ 3\right\} {3}, { 6 } \left\{ 6\right\} {6}, and { 7 } \left\{ 7\right\} {7}. It can be shown that it is impossible to make 5 5 5 connected components. Therefore, the answer is 4 4 4.

题目分析

题目要求我们在一棵树上执行两次删除操作,每次删除一个节点并断开该节点与其他节点的连接,最终求得删除操作后树的最大连通分量数。树是无环的连通图。为了得到最大的连通分量,我们需要考虑如何选择删除的节点,使得被切断的部分能产生最多的连通分量。

问题分析

本题的核心观察是,当从树中删除一个顶点时,连通分量的数量会增加 deg − 1 \text{deg}-1 deg1,其中 deg \text{deg} deg 是删除顶点时的度数。因此,删除两个顶点 i i i j j j 后的连通分量数量可以通过以下方式确定:

  • 如果 i i i j j j 不相邻,则连通分量数量为 d i + d j − 1 d_i + d_j - 1 di+dj1
  • 如果 i i i j j j 相邻,则连通分量数量为 d i + d j − 2 d_i + d_j - 2 di+dj2,因为删除 i i i 会使 j j j 的度数减少 1。

这个观察为最大化连通分量提供了一个重要的思路,接下来的两种方法可以帮助我们有效地选择删除的顶点对。

方法一:暴力枚举 第一个 顶点

这种方法的基本思路是:固定第一个顶点,然后最大化第二个顶点对连通分量的影响。具体步骤如下:

  1. 维护度数序列的有序状态:可以使用以下数据结构来维护度数序列:

    • std::multiset 来维护度数序列;
    • 优先队列(堆)或者简单的排序序列。
  2. 选择第一个顶点 i i i。固定第一个顶点后,接下来需要将所有与 i i i 相邻的顶点的度数减去 1(因为删除 i i i 会减少其邻接顶点的度数)。

  3. 一旦更新了 i i i 的邻接顶点的度数,接下来的步骤是选择度数 最大 的顶点(该顶点不与 i i i 相邻),从而最大化第二个顶点的效果。

  4. 计算删除顶点后的连通分量数量,并更新最大值。

方法一的时间复杂度:

  • 维护度数序列的时间为 O ( d log ⁡ n ) \mathcal{O}(d \log n) O(dlogn),其中 d d d 是顶点的度数。
  • 总的时间复杂度为 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn),因为树的度数和为 ∑ d = 2 ( n − 1 ) \sum d = 2(n-1) d=2(n1),即为 O ( n ) \mathcal{O}(n) O(n)

方法二:暴力枚举 第二个 顶点

第二种方法专注于贪心选择第一个顶点,其基于第一个顶点的 初始度数 来进行选择。具体思路如下:

  • 贪心地选择一个度数最大的顶点作为第一个顶点,这样至少能找到一个最优解。如果第一个顶点 i i i 的度数为 d i d_i di,且第二个顶点的度数为 d j d_j dj,那么如果选择 d i + d j − 2 d_i + d_j - 2 di+dj2 是最优的,那么任何其他的解中,如果 i ′ i' i j ′ j' j 的度数都小于 d i d_i di,那么其最大的可能值是 d i ′ + d j ′ − 1 d_{i'} + d_{j'} - 1 di+dj1,不会超过 d i + d j − 2 d_i + d_j - 2 di+dj2

  • 如果有多个顶点的度数相同,我们需要测试多个度数相同的顶点。幸运的是,只需尝试两个度数最大的顶点,即可找到最优解。这个可以通过反证法证明:如果选择了度数最大的两个顶点 u u u v v v,那么在选择第二个顶点时,至少有一对顶点( ( u , v ) (u, v) (u,v) ( u , w ) (u, w) (u,w) ( v , w ) (v, w) (v,w))是不相邻的。如果它们都是相邻的,那么就说明图中有一个三角形,意味着图不是一棵树。因此,尝试两个度数最大的顶点就能保证找到最优解。

因此,该方法的步骤是:

  1. 选择两个度数最大的顶点作为第一个顶点;
  2. 暴力枚举其余的顶点作为第二个顶点,保证找到至少一个最优解。

方法二的时间复杂度:

  • 由于我们只需要首先选择两个度数最大的顶点,因此时间复杂度为 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn),或者在最好的情况下为 O ( n ) \mathcal{O}(n) O(n),具体取决于如何检查邻接关系。
  • 如果我们通过 DFS 预处理父节点关系来进行邻接检查,那么在 O ( 1 ) \mathcal{O}(1) O(1) 时间内可以判断两个顶点是否相邻。

结论

通过这两种方法,我们能够有效地最大化删除两个顶点后的连通分量数量。两种方法都依赖于树的度数序列,并通过策略性地选择删除的顶点来达到优化效果。无论是暴力枚举第一个顶点,还是贪心选择第一个顶点并枚举第二个顶点,我们都能在时间复杂度为 O ( n log ⁡ n ) \mathcal{O}(n \log n) O(nlogn) 的情况下找到最优解。

代码实现

#include 
using namespace std;

const int N = 2e5 + 10;
int n, d[N], ans, f[N];
vector<int> e[N];

// 深度优先搜索,计算最大连通分量数
void dfs(int u, int Fa) {
    f[u] = -1e9; // 初始化每个节点的最大深度
    for (int v : e[u]) {
        if (v != Fa) {
            ans = max(ans, d[u] + d[v]); // 直接连接的两个节点会形成两个分离的部分
            dfs(v, u);
            ans = max(ans, d[u] + f[v] + 1); // 删除节点时,子树的影响
            f[v] = max(f[v], d[v]); // 更新子树的最大深度
            ans = max(ans, f[u] + f[v] + 1); // 计算删除当前节点时,形成的最大连通分量数
            f[u] = max(f[u], f[v]); // 更新当前节点的最大深度
        }
    }
}

// 清理状态,准备处理下一个测试用例
void cleared() {
    ans = 0;
    for (int i = 1; i <= n; i++) {
        e[i].clear();
        d[i] = 0; // 初始化节点的度数
    }
}

// 处理每个测试用例
void solved() {
    cin >> n;
    cleared();

    // 读取树的边信息
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
        d[u]++;
        d[v]++;
    }
    
    dfs(1, 0); // 从根节点开始进行 DFS
    cout << ans << endl; // 输出结果
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

    int T;
    cin >> T;
    while (T--) {
        solved(); // 处理每一个测试用例
    }

    return 0;
}

代码解析

  1. dfs 函数

    • 该函数采用深度优先搜索(DFS)遍历树,计算每个节点的子树深度,并通过递归更新全局变量 ans,以保持目前删除节点后的最大连通分量数。
    • 在遍历时,更新了 f[u],代表从节点 u 向下遍历的最大深度,以及根据删除节点后的影响来更新 ans
  2. cleared 函数

    • 该函数用于每次测试用例开始前清理相关的全局变量,确保每次运行是独立的。
  3. solved 函数

    • 处理单个测试用例,首先构建树的邻接表,然后调用 DFS 计算最大连通分量数,并输出结果。
  4. main 函数

    • 读取多个测试用例,并依次调用 solved 函数处理每个用例。

时间复杂度分析

  1. 每个测试用例的处理过程中,我们需要遍历树的所有节点和边。对于每个测试用例,时间复杂度为 O ( n ) O(n) O(n),其中 n n n 是节点数。

  2. 由于每个节点的度数最多为 n − 1 n-1 n1,每次 DFS 操作最多访问每条边一次,因此每个测试用例的时间复杂度为 O ( n ) O(n) O(n)

  3. 给定所有测试用例的总节点数不超过 2 × 1 0 5 2 \times 10^5 2×105,因此整体时间复杂度为 O ( ∑ n ) O(\sum n) O(n),其中 ∑ n ≤ 2 × 1 0 5 \sum n \leq 2 \times 10^5 n2×105,符合题目要求的时间限制。

示例

输入:

3
2
1 2
4
1 2
2 3
2 4
7
1 2
1 3
2 4
4 5
5 6
5 7

输出:

0
2
4

结论

通过 DFS 算法和对每个节点的度数以及子树深度的计算,我们能够有效地模拟树节点删除后的影响,并找到能够最大化连通分量数的删除操作。这个方法在时间复杂度上能够满足题目的要求,并且通过对树的深度优先遍历,得到了正确的答案。

你可能感兴趣的:(acm训练集合,搜索,dfs,数据结构)