欢迎光临
我们一直在努力

UVa 1267 Network

一、题目背景和问题描述

本题大意是n (3 \\leq n \\leq 1000) 台机器连起来成一个树状网络,其中叶子结点是客户端,其他节点是服务器。目前有一台服务器正在提供VOD(Video On Demand)服务,虽然视频质量本身不错,但是对于那些离他很远的客户端而言网络延迟却是难以接受的。现在需要在特定的节点上面安装该服务副本,使得所有的客户端距离他最近的VOD服务器距离不会超过给定的整数k,并且所安装的服务器还要尽可能的少。题目链接:

输入格式:

第一行是数据组数T。对于每一组数据:第一行为树中的节点个数n(包含服务器和客户端)下面一行包含两个整数s和k(1 \\leq s \\leq n,1 \\leq k \\leq n)。接下来的n-1行每一行包含两个整数代表两个节点之间的一条边。

输出格式:

对于每一组数据,输出一个整数即还需要安装VOD服务副本的最小值。

输入样例

 2
 14
 12 2
 1 2
 2 3
 3 4
 4 5
 5 6
 7 5
 8 5
 4 9
 10 3
 2 12
 12 14
 13 14
 14 11
 14
 3 4
 1 2
 2 3
 3 4
 4 5
 5 6
 7 5
 8 5
 4 9
 10 3
 2 12
 12 14
 13 14
 14 11

输出样例:

 1
 0

二、整体思路

根据题目意思,本题需要将无根数变成有根树,因为天然提供了一个根节点s,也就是VOD服务器所在的位置,方便我们建树。对于那些已经满足条件的客户端,我们可以当做他们不存在,如下图所示,我们可以忽略其中的13,11, 1三个叶子结点。

因为题目是需要求最小值,所以我们核心思路思路就是采用有根树+贪心策略来求解。我们将原始服务器s作为树的根,将无根数转化成为有根树,客户端距离服务器的位置需要在给定整数k以内,那么这道题目还需要利用到树的深度,天然使用BFS更加形象方便理解。

建树的过程之中采用邻接表的方法来记录给定的树状图,通过一次BFS求出给定的任意节点u的parent[u]和depth[u],也就是节点u的父节点和节点u的深度(即到根节点的距离)。要求出最小需要VOD副本数目,也就是要优先考虑最不可能被覆盖的客户端,即从深度最大的叶子结点u开始处理,需要从u向根节点的方向走k步到达节点p,在p处放置VOD副本,然后再将距离 p 不超过 k 的所有节点标记为已覆盖。如果所有的客户端均被覆盖,我们就可以得到题目所需要的答案。

假设当前需要处理的最大深度是尚未被覆盖的客户端u,从u开始向上走k步到达节点p。那么所有的可行方案都必须至少新增一个副本才有可能覆盖u,p处放置副本一定能够覆盖u。任意能够覆盖u的服务器副本都不能超过距离k,而相较于放在离u更近的节点上,放在p处能够更加靠近树的上层,并且更加容易覆盖其他没有被覆盖的客户端,所以这一个选择就可以被某一个最优方案包含,这也就体现了贪心策略的应用。

算法步骤如下:

  • 首先需要选择合适的数据结构读取树,我们可以考虑vector<vector<int>>的二维数组读取网状图,结合vector<int>存放每一个节点的父节点和他的深度,就可以记录下树里面每一个节点的信息

  • 以s为根进行BFS,计算父节点和深度

  • 找到所有读书为1的客户端,也就是叶子结点,用动态数组存图,也就是在邻接表之中,行数组的大小如果是1的话,就代表这个节点只有一条边,也就是叶子结点

  • 从s开始执行有限深度的BFS,这里的有限深度就是k,将所有当前服务器能够覆盖的范围全部标记上

  • 将客户端按照depth从大到小排序

  • 依次处理每一个客户端:如果已经覆盖那么就跳过,全部覆盖的时候就满足条件了。否则就沿着当前客户端向上走k步(这里的向上走在实际操作的时候可以利用父节点的记录连续找k次父节点)到达p,在p处放置服务器副本,计数器加1。然后从p开始继续进行有限深度的BFS,更新放置新服务器副本之后的覆盖范围。

  • 最终按照要求格式输出。本题没有特殊的格式,注意换行即可。

三、代码实现

 

#include<bits/stdc++.h>
 using namespace std;
 ​
 int main() {
     ios::sync_with_stdio(false);
     cin.tie(nullptr);
     int T;
     cin >> T;
     while (T–) {
         int n;
         cin >> n;
 ​
         int s, k;
         cin >> s >> k;
         vector<vector<int>> graph(n + 1);
         for (int i = 0; i < n – 1; ++i) {
             int u, v;
             cin >> u >> v;
             graph[u].push_back(v);
             graph[v].push_back(u);
        }
         
         vector<int> parent(n + 1, 0);
         vector<int> depth(n + 1, 0);
         queue<int> q;
         q.push(s);
         parent[s] = -1;
 ​
         while (!q.empty()) {
             int u = q.front();
             q.pop();
 ​
             for (int v : graph[u]) {
                 if (v == parent[u]) {
                     continue;
                }
 ​
                 parent[v] = u;
                 depth[v] = depth[u] + 1;
                 q.push(v);
            }
        }
 ​
         vector<int> clients;
 ​
         for (int u = 1; u <= n; ++u) {
             if (graph[u].size() == 1) {
                 clients.push_back(u);
            }
        }
 ​
         sort(clients.begin(), clients.end(),
              [&](int a, int b) {
                  return depth[a] > depth[b];
              });
 ​
         vector<bool> covered(n + 1, false);
 ​
         auto markCovered = [&](int start) {
             queue<pair<int, int>> bfs;
             vector<bool> visited(n + 1, false);
 ​
             bfs.push({start, 0});
             visited[start] = true;
 ​
             while (!bfs.empty()) {
                 auto [u, distance] = bfs.front();
                 bfs.pop();
 ​
                 covered[u] = true;
 ​
                 if (distance == k) {
                     continue;
                }
 ​
                 for (int v : graph[u]) {
                     if (!visited[v]) {
                         visited[v] = true;
                         bfs.push({v, distance + 1});
                    }
                }
            }
        };
 ​
         markCovered(s);
 ​
         int answer = 0;
 ​
         for (int client : clients) {
             if (covered[client]) {
                 continue;
            }
 ​
             int server = client;
 ​
             for (int step = 0; step < k; ++step) {
                 server = parent[server];
            }
 ​
             ++answer;
             markCovered(server);
        }
 ​
         cout << answer << '\\n';
    }
 ​
     return 0;
 }

相关解释:这份代码使用了结构化绑定以及lambda表达式的现代C++特点。

  • 结构化绑定:

  • 在 markCovered 函数中:

     while (!bfs.empty()) {
         auto [u, distance] = bfs.front();  // ← 结构化绑定
         bfs.pop();
         
         covered[u] = true;
         // …
     }

    如果不使用结构化绑定,需要这样写:

     while (!bfs.empty()) {
         auto p = bfs.front();  // p 是 pair<int, int>
         bfs.pop();
         int u = p.first;
         int distance = p.second;
         // …
     }

结构化绑定体现了现代C++简洁、可读性强的优点,这样的绑定不仅可以用在pair里面,而且可以结构化绑定数组,结构体,map元素,比如:

 map<int, string> m = {{1, "a"}, {2, "b"}};
 for (auto [key, value] : m) {
     cout << key << ":" << value << endl;
 }

这样的操作方便清晰绑定的元素,不用像以前一样使用first和second等成员来访问数据。

  • 排序函数的lambda表达式:

     sort(clients.begin(), clients.end(),
          [&](int a, int b) {
              return depth[a] > depth[b];
          });

    lambda表达式由捕获列表,参数列表,返回类型和{}之中的函数体执行。捕获列表就是制定如何访问外部的变量,lambda表达式的功能相当于一个简化函数,在捕获列表之中可以指定外部变量是否可以在函数体内部可见。[&]表示引用捕获的所有外部变量,避免了拷贝带来的额外开销。

    关于sort函数,他的第三个参数一般接受可调用对象,这里也就是接受lambda表达式。如果是函数或者lambda表达式在其参数列表里面接受两个形参,当函数体返回true的时候,参数列表的第一个参数应该排在第二个参数前面,在这里depth[a]更大的时候,a在前面,也就是实现了树的节点按照深度从大到小的排序。

赞(0)
未经允许不得转载:171主机测评 » UVa 1267 Network
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址