题解:AtCoder AT_awc0080_d Network Construction
【题目来源】
AtCoder:D - Network Construction
【题目描述】
Takahashi is the network administrator of a data center with \(N\) servers. The servers are numbered from \(1\) to \(N\).
There are \(M\) available cables for connecting servers, and the cables are also numbered from \(1\) to \(M\). The \(i\)-th cable \((1 \leq i \leq M)\) bidirectionally connects server \(U_i\) and server \(V_i\), with an installation cost of \(W_i\). Each cable connects two distinct servers (i.e., \(U_i \neq V_i\)). However, there may be multiple cables connecting the same pair of servers.
Takahashi wants to select some cables to build a network so that all \(N\) servers can communicate with each other. The set of selected cables must form a spanning tree. A spanning tree is a graph that includes all \(N\) servers as vertices, where all servers are connected by the selected cables, and no cycles exist (in this case, exactly \(N-1\) cables are selected).
However, due to security requirements, a specific set of \(K\) cables (numbered \(E_1, E_2, \dots, E_K\)) have already been contracted as dedicated encrypted communication lines and must be included in the network.
If a spanning tree exists that contains all \(K\) specified cables, output the minimum total installation cost of the \(N-1\) cables included in that spanning tree. If no such spanning tree exists, output \(-1\).
高桥是一个数据中心的网络管理员,该中心有 \(N\) 台服务器。服务器编号为 \(1\) 到 \(N\)。
有 \(M\) 条可用的电缆用于连接服务器,电缆也编号为 \(1\) 到 \(M\)。第 \(i\) 条电缆(\(1 \leq i \leq M\))双向连接服务器 \(U_i\) 和服务器 \(V_i\),安装成本为 \(W_i\)。每条电缆连接两台不同的服务器(即 \(U_i \neq V_i\))。但是,同一对服务器之间可能有多个电缆连接。
高桥希望选择一些电缆来构建一个网络,以便所有 \(N\) 台服务器可以相互通信。选中的电缆集合必须构成一棵生成树。生成树是一个图,以所有 \(N\) 台服务器为顶点,其中所有服务器通过选中的电缆连接,且不存在环(在这种情况下,恰好选中 \(N-1\) 条电缆)。
然而,由于安全要求,一组特定的 \(K\) 条电缆(编号为 \(E_1, E_2, \dots, E_K\))已经签约作为专用加密通信线路,必须包含在网络中。
如果存在一棵包含所有 \(K\) 条指定电缆的生成树,则输出该生成树中包含的 \(N-1\) 条电缆的最小总安装成本。如果不存在这样的生成树,输出 \(-1\)。
【输入】
\(N\) \(M\) \(K\)
\(U_1\) \(V_1\) \(W_1\)
\(U_2\) \(V_2\) \(W_2\)
\(\vdots\)
\(U_M\) \(V_M\) \(W_M\)
\(E_1\) \(E_2\) \(\dots\) \(E_K\)
- The first line contains the number of servers \(N\), the number of available cables \(M\), and the number of cables that must be included \(K\), separated by spaces.
- From the 2nd line to the \((M + 1)\)-th line, the information of each cable is given over \(M\) lines.
- The \((1 + i)\)-th line indicates that the \(i\)-th cable connects server \(U_i\) and server \(V_i\) with an installation cost of \(W_i\).
- If \(K \geq 1\), the \((M + 2)\)-th line contains the cable numbers \(E_1, E_2, \dots, E_K\) that must be included in the network, separated by spaces. If \(K = 0\), this line does not exist.
【输出】
If a spanning tree exists that contains all \(K\) specified cables, output in one line the minimum total installation cost of the \(N-1\) cables included in that spanning tree. If no such spanning tree exists, output \(-1\).
【输入样例】
4 5 1
1 2 3
2 3 1
1 3 2
3 4 4
2 4 5
1
【输出样例】
8
【核心思想】
-
问题分析:给定 \(N\) 个节点、\(M\) 条边的无向带权图,其中 \(K\) 条指定边必须包含在生成树中。求包含这 \(K\) 条边的最小生成树(MST)的总权重。若无法构成生成树(指定边形成环或总边数不足),输出 \(-1\)。
-
算法选择:
- Kruskal算法:基于贪心策略的最小生成树算法,按边权排序后使用并查集维护连通性
- 并查集(Union-Find):高效判断节点连通性,支持路径压缩优化
- 强制边优先策略:将必须包含的 \(K\) 条边优先排序,确保它们被选中
-
关键步骤:
- 读入 \(N\) 个节点、\(M\) 条边、\(K\) 条强制边
- 标记强制边 \(E_1, E_2, \ldots, E_K\) 的 \(flag\) 为 \(true\)
- 排序边:强制边优先(\(flag = true\)),同类型边按权重 \(W_i\) 升序
- 初始化并查集,\(p[i] = i\)
- 遍历排序后的边:
- 若两端点 \(a, b\) 不在同一集合,合并集合并累加权重
- 若两端点已在同一集合且该边为强制边,返回 \(-1\)(形成环)
- 若选中边数 \(cnt < N-1\),返回 \(-1\);否则返回总权重 \(res\)
-
时间/空间复杂度:
- 时间复杂度:\(O(M \log M + M \cdot \alpha(N))\),排序 \(O(M \log M)\),并查集操作近似 \(O(1)\)
- 空间复杂度:\(O(N + M)\),存储并查集、边数组
-
带强制边的最小生成树核心思想:
- 强制边优先策略:通过自定义排序确保强制边先被考虑,若强制边不形成环则必选
- 环检测:若强制边连接已连通的两点,则无法构成合法生成树
- Kruskal算法的扩展:在标准MST基础上增加约束条件处理
- 适用于网络设计中有预留线路、必须包含特定连接的场景
【算法标签】
生成树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005, M = 200005;
int n, m, k; // n: 节点数,m: 边数,k: 必须包含的边数
int p[N]; // 并查集数组
struct Edge
{
int a, b, w; // 边的两个端点和权重
bool flag; // 标记是否为必须包含的边
} edges[M];
bool operator<(Edge x, Edge y) // 重载小于运算符,用于排序
{
if (x.flag != y.flag) // 优先排序必须包含的边
return x.flag > y.flag;
return x.w < y.w; // 其次按权重排序
}
int find(int x) // 并查集的查找函数
{
if (p[x] != x) // 路径压缩
p[x] = find(p[x]);
return p[x];
}
int kruskal() // 最小生成树算法
{
sort(edges + 1, edges + m + 1); // 排序边
for (int i = 1; i <= n; i++) // 初始化并查集
p[i] = i;
int res = 0, cnt = 0; // res: 总权重,cnt: 已选择的边数
for (int i = 1; i <= m; i++) // 遍历所有边
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
bool isRequired = edges[i].flag;
a = find(a), b = find(b);
if (a != b) // 如果两个节点不在同一集合
{
p[a] = b; // 合并集合
res += w; // 累加权重
cnt++; // 边数加1
}
else if (isRequired) // 如果边必须包含但形成环
return -1; // 返回-1表示无解
}
if (cnt < n - 1) // 如果边数不足n-1
return -1; // 返回-1表示无解
return res; // 返回最小生成树的总权重
}
signed main()
{
cin >> n >> m >> k; // 输入节点数、边数和必须包含的边数
for (int i = 1; i <= m; i++) // 输入边的信息
{
cin >> edges[i].a >> edges[i].b >> edges[i].w;
edges[i].flag = false; // 初始化为非必须
}
for (int i = 1; i <= k; i++) // 标记必须包含的边
{
int e;
cin >> e;
edges[e].flag = true;
}
cout << kruskal() << endl; // 输出最小生成树的总权重
return 0;
}
【运行结果】
4 5 1
1 2 3
2 3 1
1 3 2
3 4 4
2 4 5
1
8
浙公网安备 33010602011771号