Codeforces 第934轮 [C] Tree Compass题解

题目详情

英文版~中文大意在下面

C. Tree Compass

You are given a tree with 𝑛𝑛 vertices numbered 1,2,…,𝑛. Initially, all vertices are colored white.

You can perform the following two-step operation:

  1. Choose a vertex 𝑣 (1≤𝑣≤𝑛) and a distance 𝑑 (0≤𝑑≤𝑛−1).
  2. For all vertices 𝑢 (1≤𝑢≤𝑛) such that dist†(𝑢,𝑣)=𝑑, color 𝑢 black.

Construct a sequence of operations to color all the nodes in the tree black using the minimum possible number of operations. It can be proven that it is always possible to do so using at most 𝑛𝑛 operations.

dist(𝑥,𝑦)enotes the number of edges on the (unique) simple path between vertices 𝑥 and 𝑦 on the tree.

Input

Each test contains multiple test cases. The first line contains a single integer 𝑡𝑡 (1≤𝑡≤200) — the number of test cases. The description of the test cases follows.

The first line of each test case contains a single integer 𝑛𝑛 (1≤𝑛≤2⋅10^3) — the number of vertices of the tree.

The following 𝑛−1 lines of each test case describe the edges of the tree. The 𝑖-th of these lines contains two integers 𝑢𝑖𝑢𝑖 and 𝑣𝑖𝑣𝑖 (1≤𝑢𝑖,𝑣𝑖≤𝑛, 𝑢𝑖≠𝑣𝑖), the indices of the vertices connected by the 𝑖-th edge.

It is guaranteed that the given edges form a tree.

It is guaranteed that the sum of 𝑛𝑛 over all test cases does not exceed 2⋅10^3.

Output

For each test case, first output a single integer 𝑜𝑝 (1≤𝑜𝑝≤𝑛), the minimum number of operations needed to color all vertices of the tree black.

Then, output 𝑜𝑝𝑜𝑝 lines, each containing 22 integers. The 𝑖𝑖-th line should contain the values of 𝑣𝑣 and 𝑑𝑑 chosen for the 𝑖-th operation (1≤𝑣≤𝑛, 0≤𝑑≤𝑛−1)

You must guarantee that at the end of 𝑜𝑝𝑜𝑝 operations, all vertices are colored black.

If there are multiple solutions, you may output any one of them.

中文大意:

这道题是关于对一棵树进行染色操作的问题。给定一个树,初始时所有节点都是白色的。你可以执行以下两步操作:

1. 选择一个节点 𝑣 和一个距离 𝑑,对于所有与节点 𝑣 的距离为 𝑑 的节点 𝑢,将其染成黑色。
2. 重复步骤1,直到所有节点都被染成黑色。

题目要求找到一种操作序列,使得染色所有节点的操作数最少。每个测试用例包含一个树的描述,以及其节点数和边的连接关系。最后需要输出操作数和每一步的操作(选择的节点和距离)。

这样做可以保证树上的所有节点都被染成黑色,而且题目保证了节点数不会超过 2000,所以需要的操作数不会超过节点数。

解题思路

对于一条线性结构,我们可以选择任意一个节点作为起始点,然后向两边延伸染色。具体地,我们可以选择中间的节点作为起始点,然后向两边延伸染色,直到达到边界。

例如对一个长度为5的现行结构,我们可以选择3作为起始点,然后向左右两边延伸染色,知道达到边界,这样就可以用两次操作完成染色。

Initial: 1 2 3 4 5 (all white)
Operation 1: Choose node 3, distance 1
Result:   1 2 *3* 4 5 (node 3 is black, marked by *)
Operation 2: Choose node 2, distance 1
Result:   1 *2* 3 *4* 5 (nodes 2 and 4 are black)

对于一般的树结构,我们可以考虑将其转化为线性结构进行染色。具体地,我们可以找到树的直径(即最长路径),然后选择直径上的中间节点作为起始点,向两边延伸染色。这样就可以用两次操作完成染色。

对于一条直线:

  • 奇数长度 (𝑛% 2 = 1):

    • 选择直线的中心节点,并进行形如 (中心节点, 𝑖) 的操作,其中 𝑖 的取值范围是 [0, ⌊𝑛/2⌋]。
  • 偶数长度 (𝑛 % 4 = 0):

    • 选择直线上的任意节点作为起点,然后进行类似于 (2, 1), (3, 1), (6, 1), (7, 1), ... 这样的操作。
  • 偶数长度但 𝑛 % 4 = 2:

    • 可以使用以上任意一种方法,因为我们有额外的一个操作,所以可以应对。

原因:

  • 当直线的长度为奇数时,我们可以选择直线的中心节点进行操作。因为直线的长度是奇数,所以直线中间有一个节点是中心,可以将其作为操作的起点。

  • 当直线的长度为偶数时,我们需要考虑一下。如果直线长度是 4 的倍数,那么我们可以按照一定规律选择节点进行操作。但如果直线长度是 4 的倍数加 2,我们就多出了一个操作,因此我们可以灵活地应对,使用与奇数长度相同的方法。

对于树结构:

直径长度奇偶性:
  • 直径长度为奇数 (𝑑% 2 = 1):

    • 找到直径的中心节点,并对每个节点执行形如 (中心节点, 𝑖) 的操作,其中 𝑖 的取值范围是 \left [ 0,\left \lfloor \frac{diameter}{2} \right \rfloor \right ]。如果这些操作没有将所有节点染色,那么说明我们找到的直径不是真实的直径,因为染色未达到直径的端点。
  • 直径长度为偶数 (𝑑% 2 = 0):

    • 找到直径的两个中心节点,并对所有奇数索引在区间 \left [ 1,\frac{diameter}{2} \right ] 的节点执行形如 (center1, 𝑖) 和 (center2, 𝑖) 的操作。当直径的长度是偶数时,我们可以找到两个直径中心节点。这是因为在一个偶数长度的路径中,不存在唯一的中心节点。所以我们可以选择任意两个距离直径中心相等的节点作为直径的中心。然后,我们可以将节点分成两组,并从两个中心节点开始,分别向两边延伸染色操作,使得每个中心节点都染色一组节点。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)1e18
#define f first
#define s second

mt19937_64 RNG(chrono::steady_clock::now().time_since_epoch().count());

void Solve() 
{
    int n;
    cin >> n;
    
    vector<vector<int>> E(n);
    
    for (int i = 1; i < n; i++){
        int u, v; cin >> u >> v;
        
        u--; v--;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    
    auto bfs = [&](int s){
      vector<int> d(n, -1);
      d[s] = 0;
      queue<int> Q;
      Q.push(s);
      while (!Q.empty()){
        int v = Q.front();
        Q.pop();
        for (int w : E[v]){
          if (d[w] == -1){
            d[w] = d[v] + 1;
            Q.push(w);
          }
        }
      }
      return d;
    };
    
    vector<int> d1 = bfs(0);
    int a = max_element(d1.begin(), d1.end()) - d1.begin();
    vector<int> d2 = bfs(a);
    int b = max_element(d2.begin(), d2.end()) - d2.begin();
    vector<int> d3 = bfs(b);
    int diam = d3[max_element(d3.begin(), d3.end()) - d3.begin()] + 1;
    //if 3 we want 1, 1 if 4 we want 1 2 
    
    vector <int> ans;
    for (int i = 0; i < n; i++){
        if ((d2[i] + d3[i] == diam - 1) && ((d2[i] == diam/2) || (d3[i] == diam/2))) 
            ans.push_back(i);
    }
    
    if (diam & 1) assert(ans.size() == 1);
    else assert(ans.size() == 2);
    
    vector <pair<int, int>> ok;
    
    if (diam & 1){
        //print everything from 0 to diam/2 
        for (int i = 0; i <= diam/2; i++){
            ok.push_back({ans[0], i});
        }
    } else {
        //2 => 2 ops, 4 => 2 ops , 6 => 4 ops, 8 => 4 ops 
        int ops = ((n - 2)/4) + 1;
        int need = (diam/2) - 1;
        while (need >= 0){
            ok.push_back({ans[0], need});
            ok.push_back({ans[1], need});
            
            need -= 2;
        }
    }
    
    cout << ok.size() << "\n";
    for (auto [u, r] : ok){
        cout << u + 1 << " " << r << "\n";
    }
}

int32_t main() 
{
    auto begin = std::chrono::high_resolution_clock::now();
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int t = 1;
    cin >> t;
    for(int i = 1; i <= t; i++) 
    {
        //cout << "Case #" << i << ": ";
        Solve();
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
   // cerr << "Time measured: " << elapsed.count() * 1e-9 << " seconds.\n"; 
    return 0;
}

算法分析

  1. 构建邻接矩阵: 首先,代码读入了树的节点数和边的信息,并构建了一个邻接矩阵来表示树的结构。这一部分的时间复杂度是 O(n),其中 n 是树的节点数。

  2. 宽度优先搜索(BFS): 接下来,代码通过三次宽度优先搜索来找到树的直径。每次宽度优先搜索的时间复杂度是 O(n+m),其中 n 是节点数,m 是边数。因为是树结构,所以 m = n-1,所以每次搜索的时间复杂度可以简化为 O(n)。

  3. 计算直径: 找到树的直径需要进行三次宽度优先搜索,因此时间复杂度是 O(n)。

  4. 构造输出结果: 算法通过分析直径的长度来确定输出。如果直径长度是奇数,那么只有一个直径中心节点,如果是偶数,有两个直径中心节点。时间复杂度是 O(d),其中 d 是直径长度。

时间复杂度是 O(n)。因为树是一个连通且无环的图,所以宽度优先搜索的时间复杂度可以简化为 O(n),而且树的直径长度不会超过树的节点数,因此可以认为算法的时间复杂度是线性的。

  • 31
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值