AcWing 2325. 有向图破坏 题解
题目分析
本题中爱丽丝绘制了一个有向图,鲍勃需要通过移除图中的边来毁掉这个图。每一步操作,鲍勃可以选择一个点,移除该点的所有入边或者所有出边,不同操作有不同的花费。要求计算鲍勃移除所有边的最少花费,并输出相关操作信息。
解题思路
本题可以转化为最小割问题,利用网络流算法求解。通过构建一个网络流图,将源点与表示点的入边操作的节点相连,边权为移除入边的花费;将表示点的出边操作的节点与汇点相连,边权为移除出边的花费;对于图中的有向边,在对应的节点之间连边,边权设为无穷大。这样,最小割的值就是移除所有边的最少花费,再通过深度优先搜索确定具体的操作点。
代码逐段分析
- 头文件和全局变量定义
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210, M = 5200 * 2 + 10, INF = 1e8;
int n, m, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
bool st[N];
- 引入必要的头文件,用于输入输出、字符串操作和算法相关功能。
- 定义常量(N)(最大节点数)、(M)(最大边数)、(INF)(无穷大)。
- 变量(n)表示图的节点数,(m)表示边数,(S)为源点,(T)为汇点。
- (h)、(e)、(ne)、(idx)用于构建邻接表存储图的边,(f)数组记录边的容量。
- (q)、(d)、(cur)用于广度优先搜索(BFS)和增广路径查找。
-
(st)数组用于标记节点是否被访问过。
-
添加边函数
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函数,计算最大流(等价于最小割)并返回。
- DFS函数
void dfs(int u)
{
st[u] = true;
for (int i = h[u]; ~i; i = ne[i])
if (f[i] && !st[e[i]])
dfs(e[i]);
}
深度优先搜索函数,从源点(S)开始,标记访问过的节点。遍历当前节点的所有邻边,对未访问且边容量大于(0)的节点继续进行DFS。
- 主函数
int main()
{
scanf("%d%d", &n, &m);
S = 0, T = n * 2 + 1;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i ++ )
{
int w;
scanf("%d", &w);
add(S, i, w);
}
for (int i = 1; i <= n; i ++ )
{
int w;
scanf("%d", &w);
add(n + i, T, w);
}
- 读取图的节点数(n)和边数(m),设置源点(S = 0)和汇点(T = n * 2 + 1),初始化邻接表。
- 读取每个点移除入边的花费(w),从源点(S)向表示该点入边操作的节点连边,边权为(w)。
- 读取每个点移除出边的花费(w),从表示该点出边操作的节点向汇点(T)连边,边权为(w)。
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add(b, n + a, INF);
}
读取图中的有向边,从边的终点(b)向表示起点(a)出边操作的节点连边,边权设为无穷大。
printf("%d\n", dinic());
dfs(S);
调用(dinic)函数计算最小割(即最少花费)并输出,然后从源点(S)开始进行DFS。
int cnt = 0;
for (int i = 0; i < idx; i += 2)
{
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b]) cnt ++ ;
}
统计被源点DFS到的节点与未被DFS到的节点之间的边的数量(cnt),这些边对应着需要进行的操作。
printf("%d\n", cnt);
for (int i = 0; i < idx; i += 2)
{
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b])
{
if (a == S) printf("%d +\n", b);
}
}
for (int i = 0; i < idx; i += 2)
{
int a = e[i ^ 1], b = e[i];
if (st[a] && !st[b])
{
if (b == T) printf("%d -\n", a - n);
}
}
输出操作的数量(cnt),然后分别输出移除入边和移除出边的操作点信息。如果与源点相连的边的另一端点未被DFS到,则输出该点编号和“+\”,表示移除该点的入边;如果与汇点相连的边的另一端点被DFS到,则输出该点编号减去(n)和“-\”,表示移除该点的出边。
时间复杂度分析
- 构建网络流图的时间复杂度为(O(n + m))。
- (dinic)算法的时间复杂度为(O(n^2m)),这里(n)是节点数,(m)是边数。
- 深度优先搜索的时间复杂度为(O(n + m))。
- 总的时间复杂度为(O(n^2m))。
空间复杂度分析
- 图的存储使用邻接表,边数为(m),所以存储图的空间复杂度为(O(m))。
- 其他辅助数组如(q)、(d)、(cur)、(st)等,大小与节点数(n)有关,为(O(n))。
- 总的空间复杂度为(O(n + m))。