网络流 最大流

网络流 最大流算法

网络

图论中的网络是一种特殊的有向图,与有向图不同的是网络存在容量、源点和汇点

  • \(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)\) 级别,在稠密图中的运行效率很优

posted @ 2025-09-02 20:52  才瓯  阅读(6)  评论(0)    收藏  举报