网络流小记

I.基本定义:

  • 网络:一张有向图。

  • 流量:经过一条边的流的大小,一条边 \((u,v)\) 的流量记为 \(flow(u,v)\), 一个网络的流量定义为 \(∑f(s,x)\)

  • 容量:一条边的流量上限,一条边 \((u,v)\) 的容量记为 \(cap(u,v)\)

  • 费用:经过一条边单位流量的所需费用,一条边 \((u,v)\) 的费用记为 \(cost(u,v)\)

  • 源点:所有流的起始点, 记为 \(s\)

  • 汇点:所有流的终止点,记为 \(t\)

  • 割:是网络的一个划分, 一个割 {\(S, T\)} 的容量定义为 \(||S, T|| = \sum_{u \in S} \sum_{v \in T} cap(u, v)\)

  • 残量网络:每条边剩余可走的流量组成的网络。

网络流的性质:

  1. 斜对称性:\(flow(u,v) = -flow(v,u)\)

  2. 流量守恒:\(\sum_{u \ne x, t\ne y} flow(u, x) = \sum flow(y,u)\)

  3. 容量限制: \(flow(u,v) \le cap(u,v)\)

II.一些定理:

增广路定理

  • 增广路:在残量网络中的一条 \(s\)\(t\) 的路径,满足路径上的残量均大于 \(0\)

一个流为最大流当且仅当网络中没有增广路,证明显然。

最大流最小割定理:

对于任意网络,最大流 \(f\) 和最小割 \(\{S, T\}\) 总是满足 \(|f| = ||S, T||\)

  • 证明:首先显然有 \(|f| \le ||S, T||\),因为根据割的定义 \(S, T\) 互不相交,\(S -> T\) 的流量一定大于等于 \(|f|\),考虑如何构造取到等号。

III.最大流:

EK 算法:

对于求一张图的最大流,我们考虑引入反向边的概念。

  • 反向边:一条边的反向边定义为一条流向与原边相反,容量为 \(0\),流量与原边相反的边。

经过反向边相当于做退流操作,类似于反悔,这也是 EK 算法的关键之处。具体的,我们将反向边一并加入到残量网络中,并与原边一起考虑。

由上面的定理,我们可以得到以下算法:

  1. 在残量网络上找一条增广路,将这条路径上的流大小记为 \(flow\)

  2. 将路径上的所有边加上 \(flow\),反向边减掉 \(flow\)

我们暴力的使用 \(dfs\) 去找,时间复杂度 \(O(nm^2)\)

Dinic 算法:

考虑优化上面的算法,\(\rm EK\) 算法一次只找到一条增广路,这使得有一些无用的操作,考虑一次进行多路增广,即一次找到多条增广路。

我们考虑对残量网络进行分层,这样我们可以一次找到多条长度相同的增广路并进行增广。分层容易用 \(\rm BFS\) 实现。

接着我们考虑进行多路增广,这个使用 \(\rm DFS\) 也不难实现。这里有一个叫做当前弧优化的东西,作用是让已经考虑过的边不再考虑,可以保证时间复杂度在 \(O(n^2m)\),实现开一个数组记录即可。建议结合代码食用。

qwq

一些例题:

this

(有待修缮)

IV.费用流:

在费用流问题中,每条边多了一个元素称作费用,一个流的费用等于该流经过的边的费用和乘上流的大小。费用流问题中最经典的是最小费用最大流问题,即在最大化流大小的情况下最小化费用和。

其实这个问题非常简单,我们每次选择流最大的增广路中最小费用的进行增广即可。

qwq
namespace Dinic{
	struct edge{
		int v, flow, cap, cost, next;
	}edges[M << 1];
	int head[N], idx = 1;
	int cur[N], dis[N], s, t, siz, maxflow, mincost;
	bool vis[N]; 
	void add_edge(int u, int v, int cap, int cost){
		edges[++idx] = {v, 0, cap, cost, head[u]};
		head[u] = idx;
	}
	void addline(int u, int v, int cap, int cost){add_edge(u, v, cap, cost); add_edge(v, u, 0, -cost);}
	bool SPFA(){
		for(int i = 1; i <= siz; i++) dis[i] = INF, cur[i] = head[i], vis[i] = false;
		dis[s] = 0; queue<int> Q; Q.push(s);
		while(!Q.empty()){
			int u = Q.front(); Q.pop();
			vis[u] = false;
			for(int i = head[u]; i; i = edges[i].next){
				int v = edges[i].v;
				if(dis[v] > dis[u] + edges[i].cost && edges[i].cap > edges[i].flow){
					dis[v] = dis[u] + edges[i].cost;
					if(!vis[v]) Q.push(v), vis[v] = true;
				}
			}
		}
		for(int i = 1; i <= siz; i++) vis[i] = false;
		return (dis[t] != INF);
	}
	int dfs(int u, int flow, int &cost){
		if((!flow) || u == t) return flow;
		int ret = 0; vis[u] = true;
		for(int& i = cur[u]; i; i = edges[i].next){
			int v = edges[i].v, d;
			if((!vis[v]) && dis[v] == dis[u] + edges[i].cost && (d = dfs(v, min(flow - ret, edges[i].cap - edges[i].flow), cost))){
				ret += d; edges[i].flow += d; edges[i ^ 1].flow -= d; cost += edges[i].cost * d;
				if(flow == ret) return flow;
			} 
		}
		vis[u] = false;
		return ret;
	}
	void dinic(){
		while(SPFA()){
			int cost = 0;
			maxflow += dfs(s, INF, cost); mincost += cost;
		}
	}
}

V.上下界网络流:

现在我们在普通网络流的基础上再给每条边增加一个属性:流量上界,即经过一条边的流量至少要大于等于流量下界。

1.无源汇上下界可行流:

对于一张

posted @ 2024-04-24 13:12  Little_corn  阅读(31)  评论(0编辑  收藏  举报