AcWing 381. 有线电视网络 题解
题目分析
本题给定一张无向图,要求找出最少去掉多少个点可以使图不连通。如果无论去掉多少个点图都保持连通,则返回图的节点总数(n)。
解题思路
本题可通过网络流算法来求解。将每个点拆分成两个点,一个入点和一个出点,入点和出点之间连一条容量为(1)的边,代表该点是否被去掉(容量为(1)意味着最多只能通过(1)的流量,即最多去掉(1)次)。对于原图中的边,在对应的入点和出点之间连容量为无穷大的边。然后枚举源点和汇点,通过求最小割来确定最少去掉多少个点能使图不连通。
代码逐段分析
- 头文件和全局变量定义
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 5210, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
- 引入必要的头文件,用于输入输出、字符串操作和算法相关功能。
- 定义常量(N)(最大节点数)、(M)(最大边数)、(INF)(无穷大)。
- 变量(n)表示图的节点数,(m)表示边数,(S)为源点,(T)为汇点。
- (h)、(e)、(ne)、(idx)用于构建邻接表存储图的边,(f)数组记录边的容量。
-
(q)、(d)、(cur)用于广度优先搜索(BFS)和增广路径查找。
-
添加边函数
void add(int a, int b, int c)
{
e[idx] = b, f[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
e[idx] = a, f[idx] = 0, ne[idx] = h[b], h[b] = idx ++ ;
}
在图中添加一条从(a)到(b)容量为(c)的边,同时添加反向边(容量为(0))用于网络流的反向增广。
- BFS函数(构建层次图)
bool bfs()
{
int hh = 0, tt = 0;
memset(d, -1, sizeof d);
q[0] = S, d[S] = 0, cur[S] = h[S];
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int ver = e[i];
if (d[ver] == -1 && f[i])
{
d[ver] = d[t] + 1;
cur[ver] = h[ver];
if (ver == T) return true;
q[ ++ tt] = ver;
}
}
}
return false;
}
使用BFS构建层次图,标记每个节点的层次。若能到达汇点(T),则返回(true),表示存在从源点到汇点的路径;否则返回(false)。
- 增广路径查找函数
int find(int u, int limit)
{
if (u == T) return limit;
int flow = 0;
for (int i = cur[u]; ~i && flow < limit; i = ne[i])
{
cur[u] = i;
int ver = e[i];
if (d[ver] == d[u] + 1 && f[i])
{
int t = find(ver, min(f[i], limit - flow));
if (!t) d[ver] = -1;
f[i] -= t, f[i ^ 1] += t, flow += t;
}
}
return flow;
}
在层次图中从节点(u)开始,沿着层次递增的路径寻找增广路径。若到达汇点(T),则返回当前的流量限制(limit)。在遍历边的过程中,若满足层次关系且边有剩余容量,则递归地在子节点中寻找增广路径,并更新边的容量。若某条路径无法继续增广,则将该节点的层次标记为(-1)。
- Dinic算法函数
int dinic()
{
int r = 0, flow;
while (bfs()) while (flow = find(S, INF)) r += flow;
return r;
}
不断调用BFS和find函数,计算最大流(等价于最小割)并返回。
- 主函数
int main()
{
while (cin >> n >> m)
{
memset(h, -1, sizeof h);
idx = 0;
for (int i = 0; i < n; i ++ ) add(i, n + i, 1);
- 进入循环,持续读取节点数(n)和边数(m),直到输入结束。
- 初始化邻接表,将(h)数组所有元素设为(-1),(idx)设为(0)。
- 遍历每个节点(i),将节点(i)的入点(编号为(i))和出点(编号为(n + i))相连,边容量设为(1)。
while (m -- )
{
int a, b;
scanf(" (%d,%d)", &a, &b);
add(n + a, b, INF);
add(n + b, a, INF);
}
遍历每条边,读取边的两个端点(a)和(b)。在对应的入点和出点之间连边,即从(n + a)到(b)、从(n + b)到(a)连边,边容量设为无穷大(INF)。
int res = n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < i; j ++ )
{
S = n + i, T = j;
for (int k = 0; k < idx; k += 2)
{
f[k] += f[k ^ 1];
f[k ^ 1] = 0;
}
res = min(res, dinic());
}
- 初始化结果变量(res)为(n)。
- 枚举所有可能的源点(S)(节点(i)的出点(n + i))和汇点(T)(节点(j),且(j < i))。
- 在每次枚举前,将所有边的容量更新(把反向边的容量加到正向边上,然后将反向边容量设为(0))。
- 调用(dinic)函数计算最小割,并更新(res)为当前最小割和(res)中的较小值。
printf("%d\n", res);
}
return 0;
}
输出最终结果(res),即最少去掉的节点数,若无论如何都无法使图不连通,则(res)为(n)。
时间复杂度分析
- 构建网络流图的时间复杂度为(O(n + m)),因为需要处理每个节点和每条边。
- 枚举源点和汇点的时间复杂度为(O(n^2)),每次枚举调用(dinic)算法,(dinic)算法的时间复杂度为(O(n^2m))(这里(n)是节点数,(m)是边数)。
- 总的时间复杂度为(O(n^4m))。
空间复杂度分析
- 图的存储使用邻接表,边数最多为(O(m)),所以存储图的空间复杂度为(O(m))。
- 其他辅助数组如(q)、(d)、(cur)等,大小与节点数有关,为(O(n))。
- 总的空间复杂度为(O(n + m))。