AcWing 2176. 太空飞行计划问题 题解
题目分析
本题中(W)教授要为国家航天中心计划太空飞行,存在一个实验集合和仪器集合。每个实验有对应的收益,进行实验需要用到部分仪器,每个仪器有配置费用。目标是找出能使太空飞行净收益(实验收益总和 - 仪器配置费用总和)最大的实验和仪器配置方案。
解题思路
将此问题转化为最大权闭合图问题,利用网络流算法求解。构建一个网络流图,设立源点和汇点。源点与每个实验节点相连,边权为该实验的收益;每个仪器节点与汇点相连,边权为该仪器的配置费用;如果某个实验需要某仪器,则从该实验节点向对应的仪器节点连边,边权设为无穷大。这样,通过求最小割(最大流),用总收益减去最小割的值,就能得到最大净收益,同时可以通过深度优先搜索确定具体的实验和仪器选择方案。
代码逐段分析
- 头文件和全局变量定义
#include <iostream>
#include <cstring>
#include <sstream>
#include <algorithm>
using namespace std;
const int N = 110, M = 5210, INF = 1e8;
int m, n, S, T;
int h[N], e[M], f[M], ne[M], idx;
int q[N], d[N], cur[N];
bool st[N];
- 引入必要的头文件,
iostream
用于输入输出,cstring
用于字符串操作,sstream
用于字符串流处理,algorithm
用于算法相关功能。 - 定义常量(N)(最大节点数)、(M)(最大边数)、(INF)(无穷大)。
- 变量(m)表示实验数量,(n)表示仪器数量,(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 (!st[e[i]] && f[i])
dfs(e[i]);
}
深度优先搜索函数,从源点(S)开始,标记访问过的节点。遍历当前节点的所有邻边,对未访问且边容量大于(0)的节点继续进行DFS。
- 主函数
int main()
{
scanf("%d%d", &m, &n);
S = 0, T = m + n + 1;
memset(h, -1, sizeof h);
getchar(); // 过滤掉第一行最后的回车
- 读取实验数量(m)和仪器数量(n),设置源点(S = 0)和汇点(T = m + n + 1),初始化邻接表。
- 使用
getchar()
过滤掉输入第一行最后的回车符。
int tot = 0;
for (int i = 1; i <= m; i ++ )
{
int w, id;
string line;
getline(cin, line);
stringstream ssin(line);
ssin >> w;
add(S, i, w);
while (ssin >> id) add(i, m + id, INF);
tot += w;
}
- 初始化总收益(tot = 0)。
- 遍历每个实验:
- 使用
getline(cin, line)
读取一行实验数据,通过stringstream
将其按空格分割。 - 读取实验收益(w),从源点(S)向该实验节点连边,边权为(w)。
- 继续读取该实验需要的仪器编号(id),从该实验节点向对应的仪器节点(编号为(m + id))连边,边权设为无穷大(INF)。
- 将该实验的收益累加到(tot)中。
- 使用
for (int i = 1; i <= n; i ++ )
{
int p;
cin >> p;
add(m + i, T, p);
}
遍历每个仪器,读取其配置费用(p),从该仪器节点(编号为(m + i))向汇点(T)连边,边权为(p)。
int res = dinic();
dfs(S);
调用dinic
函数计算最小割(即需要舍去的收益和费用之和),然后从源点(S)开始进行DFS。
for (int i = 1; i <= m; i ++ )
if (st[i]) printf("%d ", i);
puts("");
for (int i = m + 1; i <= m + n; i ++ )
if (st[i]) printf("%d ", i - m);
puts("");
printf("%d\n", tot - res);
return 0;
}
- 输出被选中的实验编号(被源点DFS到的实验节点)。
- 输出被选中的仪器编号(被源点DFS到的仪器节点编号减去(m))。
- 输出最大净收益,即总收益(tot)减去最小割的值(res)。
时间复杂度分析
- 构建网络流图的时间复杂度为(O(mn)),因为需要处理每个实验和仪器之间的关系。
- (dinic)算法的时间复杂度为(O(n^2m)),这里(n)和(m)分别是仪器数和实验数。
- 深度优先搜索的时间复杂度为(O(n + m))。
- 总的时间复杂度为(O(n^2m))。
空间复杂度分析
- 图的存储使用邻接表,边数最多为(O(mn)),所以存储图的空间复杂度为(O(mn))。
- 其他辅助数组如(q)、(d)、(cur)、(st)等,大小与节点数有关,节点数为(O(m + n)),所以这些辅助数组的空间复杂度为(O(m + n))。
- 总的空间复杂度为(O(mn))。