网络
图论中的网络是一种特殊的有向图,与有向图不同的是网络存在容量、源点和汇点
- $E$ 边集中的每一条边 $(u,v)$ 都有一个被称为容量的权值 $c$
- $V$ 点集中存在两个特殊点:源点 $s$ 和汇点 $t$
在网络 $G=(V,E)$ 中,流是一个从边集到整数集的一个函数,$f(u,v)$ 表示边 $(u,v)$ 上的流量,$c(u,v)-f(u,v)$ 表示这条边的剩余容量
在网络中,合法的可行的流应该满足性质:
- 容量限制:$0\le f(u,v)\le c(u,v)$
- 流量守恒:$\sum\limits_{(u,x)\in E} f(u,x)=\sum\limits_{(x,v)\in E} f(x,v),x\ne \{s,t\} $ ,形象化的说,一个普通点流入的容量等于流出的容量
最大流问题
我们希望在网络 $G=(V,E)$ 中找到合适的流 $f$ ,最大化这个网络的流量 $\lvert f\rvert =\sum\limits_{x\in V}f(s,x)-\sum\limits_{x\in V}f(x,s)$
增广路: 一条从源点到汇点的路径,路径上所有边的剩余容量都大于 $0$
残留网: 网络中所有结点和剩余容量大于 $0$ 的所有边构成的子图
EK算法
在建图时给每个有向边都构建一条反向边,容量为 $0$ ,目的是提供一条退流的路径,一旦前方的增广路无法连通可行流,那么就可以通过退流管道将增广路的部分流量退回
因为需要快速的定位反向边,所以采用链式前向星的方式存图
struct edge{int v; LL c; int ne;};
edge e[M];
int h[210], idx = 1;//从2开始给边进行编号,0是哨兵
void add(int u, int v, LL c){
e[++idx] = {v, c, h[u]};
h[u] = idx;
}
for (int i = 1; i <= m; i++){
int u, v;
LL c;
cin >> u >> v >> c;
add(u, v, c);
add(v, u, 0);
}
通过 BFS 找到一条增广路
类似贪心的扩展,每次只走到一个没有被访问过的节点
int pre[210];//记录节点的前驱边
LL mf[210];//s到该点的流量上限
bool bfs(void){
memset(mf, 0, sizeof mf);
queue<int> q;
q.push(s);
mf[s] = 1e9;
while (q.size()){
int u = q.front();
q.pop();
for (int i = h[u]; i; i = e[i].ne){//枚举所有出边
int v = e[i].v;
if (mf[v] == 0 && e[i].c){//只有这个邻点没有被访问过且有容量
mf[v] = min(mf[u], e[i].c);
pre[v] = i;
q.push(v);
if (v == t) return 1;//能走到汇点
}
}
}
return 0;//不能走到汇点,说明无可行流
}
每轮 BFS 都找到一条可行流,累加这些流的流量,然后构建残留网
LL EK(void){
LL flow = 0;
while (bfs()){//存在可行流时
int v=t;//从汇点出发
while (v != s){//回溯到源点
int i = pre[v];
e[i].c -= mf[t];
e[i^1].c += mf[t];//更新退流管道
v = e[i^1].v;//走反向边
}
flow += mf[t];
}
return flow;//返回最大流
}
总的时间复杂度上界为 $O(nm^2)$ 的级别,$n,m$ 分别为点数和边数,实际上会比这快得多
Dinic算法
区别于 EK 算法每一轮只累加一条增广路的流量,我们可以先将点 BFS 分层然后通过 DFS 寻找多条增广路
LL dfs(int u, LL mf){
if (u == t) return mf;//如果到终点,返回流量
LL res = 0;
for (int i = cur[u]; i; i = e[i].ne){
cur[u] = i;//当前弧优化
int v = e[i].v;
if (dep[v] == dep[u] + 1 && e[i].w){//只有当下一个节点是下一层时才搜索
LL f = dfs(v, min(mf, e[i].w));//流量限制
e[i].w -= f;
e[i ^ 1].w += f;//退流
res += f;//累加当前流量
mf -= f;
if (mf == 0) break;//如果当前节点以及没有流量就立即返回
}
}
if (res == 0) dep[u] = 0;//残枝优化
return res;
}
其中的当前弧 cur[u]
表示节点 $u$ 在链式前向星中已经考虑过了哪些边,再次进入循环时就可以跳过这些边
残枝优化的意义时,如果当前节点的 res
为 $0$ ,这个节点就不会连通到汇点,所以在当前的残留网中就不需要考虑了
LL dinic(){
LL flow = 0;
while (bfs()){
memcpy(cur, h, sizeof h);//重置当前弧
flow += dfs(s, 1e9);//累加可行流
}
return flow;
}
时间复杂度为 $O(n^2m)$ 级别,在稠密图中的运行效率很优
评论区(暂无评论)