网络最大流 Dinic算法

原博客地址

前言

看到网上好多都用的链式前向星,就我在用 v e c t o r vector vector ……

定义

先来介绍一些相关的定义。(个人理解)

网络

一个网络是一张带权的有向图 G = ( V , E ) G=(V,E) G=(V,E) ,其中每任意一条边 ( u , v ) (u,v) (u,v) 的权值称为这条边的容量 c ( u , v ) c(u,v) c(u,v) 。若这条边不存在,对应的容量就为 0 0 0 。其中包含两个特殊的点:源点 S S S 与汇点 T T T

流量

f f f 为网络的流函数,每一条边都有对应的流量。对于合法的流函数包含以下性质。

  1. 容量限制: f ( u , v ) ≤ c ( u , v ) f(u,v)≤c(u,v) f(u,v)c(u,v)
  2. 斜对称: f ( u , v ) = − f ( v , u ) f(u,v)=-f(v,u) f(u,v)=f(v,u)
  3. 流量守恒:对于任意满足不为源点或汇点的节点 k k k ,有: ∑ u ∈ E f ( u , k ) = ∑ v ∈ E f ( k , v ) ∑_{u∈E}f(u,k)=∑_{v∈E}f(k,v) uEf(u,k)=vEf(k,v)

最大流

对于一个网络,不难发现合法的流函数很多。这张图的流量为 ∑ k ∈ E f ( S , k ) ∑_{k∈E}f(S,k) kEf(S,k) ,顾名思义,最大流就是这张网络的最大流量。

增广路

存在一条从 S S S T T T 的路径,使得路径上所有的流量都不为 0 0 0 ,则称该路径为增广路。

残量网络

对于任意时刻,当前时刻网络中,由所有结点与剩余容量大于 0 0 0 的边构成的该网络的子图被称为残量网络。

分层图

在这个算法中,将残量网络分层后所构成的图称为分层图。

Dinic算法

建图

一条边中需要包含以下信息:终点节点编号,边的容量,相反的边的编号。

struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, LL R) {
		to = T;//节点编号
		value = V;//边的容量
		rev = R;//相反的边的编号
	}
};

得双向存边,给出一条边 ( A , B ) (A,B) (A,B) ,其长度为 C C C ,建一条从 A A A B B B 的边,权值为 C C C ,与之相反的边权值为 0 0 0

for(int i = 1; i <= m; i++) {
	Quick_Read(A);
	Quick_Read(B);
	Quick_Read(C);
	int idA = v[A].size(), idB = v[B].size();
	v[A].push_back(Node(B, C, idB));
	v[B].push_back(Node(A, 0, idA));
}

双向存边是为了给后面 d f s dfs dfs 时,若存在更优解,可以使得程序反悔,重新走另一条路。这里暂时不懂可以继续看后面的代码再来理解这样建图的意义。

主体部分

  • 一遍 b f s bfs bfs ,将残量网络构造成分层图,并求出当前残量网络是否存在增广路。
  • 一遍 d f s dfs dfs ,在该分层图中寻找增广路,将这条让这条增广路消失。

重复上述两个操作,直到当前网络中不存在增广路。

先来看 b f s bfs bfs 。其返回值为 b o o l bool bool ,意为该残量网络中是否还存在增广路。层数 d e [ i ] de[i] de[i] 的意义很明白: S S S 到达当前的点 i i i 的最小步数。而按照这样的分层,每次只能将当前流量信息传递到下一层数节点上,可以很大程度上避免张冠李戴的情况。若 T T T 的层数为 1 1 1 ,则说明当前 S S S 不能通向 T T T ,故而不存在增广路,跳出循环。

bool bfs_Dinic() {//bfs将残量网络分层,返回是否图中还存有增广路 
	memset(de, 0, sizeof(de));//清空深度
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;//分层图改变,必须改变be[next]的值
				de[next] = de[now] + 1;
				if(next == t)
					return true;
			}
		}
	}
	return false;
}

再来看 d f s dfs dfs ,来判断每一次的网络是否可以传递,完成增广的过程(以下代码附上注释)。这样一次走了不止 1 1 1 条增广路,节省了不少时间。

int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)//找到汇点
		return flow;
	int i, surp = flow;//当前剩余流量 
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;//i之前的增广路已经更新完,不加否则会很慢!!
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {//&&前判断是否可以走,即是剩余流量是否为0;&&后判断是否满足当前残余网络分层要求
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)//经验定增广完毕,de这个点不需要在遍历了
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;//反悔,可能找到其他路径比当前这个流大 
			surp -= maxnow;
		}
	}
	return flow - surp;
}

最后在来说说剪枝, b e be be 数组,在遍历 i i i 时, b e [ i ] be[i] be[i] 之前的路径已经找完增广路了,而对于当前这个分层图,不存在会有更优解的情况,程序也不需要反悔,并不会影响程序的正确性,所以直接就不需要遍历之前的点。

时间复杂度

单看这段程序,可以发现时间复杂度为 O ( n 2 m ) O(n^2m) O(n2m)

int Dinic() {
	int res = 0, flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))//最大流的定义 
			res += flow;//流量守恒 
	return res;
}

而其实实际上并不需要这么多时间,参考资料得知可以处理 1 0 4 10^4 104~ 1 0 5 10^5 105这样规模的网络。

C++代码

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
void Quick_Read(int &N) {
	N = 0;
	int op = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')
			op = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		N = (N << 1) + (N << 3) + (c ^ 48);
		c = getchar();
	}
	N *= op;
}
const int MAXN = 2e2 + 5;
struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, int R) {
		to = T;
		value = V;
		rev = R;
	}
};
vector<Node> v[MAXN];
queue<int> q;
int de[MAXN], be[MAXN];
int n, m, s, t;
bool bfs_Dinic() {
	bool flag = false;
	memset(de, 0, sizeof(de));
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;
				de[next] = de[now] + 1;
				if(next == t)
					flag = true;
			}
		}
	}
	return flag;
}
int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)
		return flow;
	int i, surp = flow;
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;
			surp -= maxnow;
		}
	}
	return flow - surp;
}
long long Dinic() {
	long long res = 0;
	int flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))
			res += flow * 1LL;
	return res;
}
void Read() {
	int A, B, C;
	Quick_Read(n);
	Quick_Read(m);
	Quick_Read(s);
	Quick_Read(t);
	for(int i = 1; i <= m; i++) {
		Quick_Read(A);
		Quick_Read(B);
		Quick_Read(C);
		int idA = v[A].size(), idB = v[B].size();
		v[A].push_back(Node(B, C, idB));
		v[B].push_back(Node(A, 0, idA));
	}
}
int main() {
	Read();
	printf("%lld", Dinic());
	return 0;
}

看看自己做对没有吖

网络最大流还可以应用于二分图最大匹配。每条边的左部点向右部点相连,从源点到每个左部点建一条边,右部点与汇点建一条边,每条边的容量为1。

(附:匈牙利算法)

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
#define INF 0x3f3f3f3f
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Max(a, b) ((a) > (b) ? (a) : (b))
void Quick_Read(int &N) {
	N = 0;
	int op = 1;
	char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-')
			op = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9') {
		N = (N << 1) + (N << 3) + (c ^ 48);
		c = getchar();
	}
	N *= op;
}
const int MAXN = 15e2 + 5;
struct Node {
	int to, value, rev;
	Node() {}
	Node(int T, int V, int R) {
		to = T;
		value = V;
		rev = R;
	}
};
vector<Node> v[MAXN];
queue<int> q;
int de[MAXN], be[MAXN];
int n, m, e, s, t;
bool bfs_Dinic() {
	bool flag = false;
	memset(de, 0, sizeof(de));
	while(!q.empty())
		q.pop();
	q.push(s);
	de[s] = 1; be[s] = 0;
	while(!q.empty()) {
		int now = q.front();
		q.pop();
		int SIZ = v[now].size();
		for(int i = 0; i < SIZ; i++) {
			int next = v[now][i].to;
			if(v[now][i].value && !de[next]) {
				q.push(next);
				be[next] = 0;
				de[next] = de[now] + 1;
				if(next == t)
					flag = true;
			}
		}
	}
	return flag;
}
int dfs_Dinic(int now, int flow) {
	if(now == t || !flow)
		return flow;
	int i, surp = flow;
	int SIZ = v[now].size();
	for(i = be[now]; i < SIZ && surp; i++) {
		be[now] = i;
		int next = v[now][i].to, valedge = v[now][i].value;
		if(valedge && de[next] == de[now] + 1) {
			int maxnow = dfs_Dinic(next, Min(surp, valedge));
			if(!maxnow)
				de[next] = 0;
			v[now][i].value -= maxnow;
			v[next][v[now][i].rev].value += maxnow;
			surp -= maxnow;
		}
	}
	return flow - surp;
}
int Dinic() {
	int res = 0;
	int flow = 0;
	while(bfs_Dinic())
		while(flow = dfs_Dinic(s, INF))
			res += flow;
	return res;
}
void Read() {
	int A, B;
	Quick_Read(n);
	Quick_Read(m);
	Quick_Read(e);
	t = n + m + 1;
	for(int i = 1; i <= e; i++) {
		Quick_Read(A);
		Quick_Read(B);
		B += Max(n, m);
		int idA = v[A].size();
		int idB = v[B].size();
		v[A].push_back(Node(B, 1, idB));
		v[B].push_back(Node(A, 0, idA));
	}
	for(int i = 1; i <= n; i++) {
		int idi = v[i].size();
		int ids = v[s].size();
		v[s].push_back(Node(i, 1, idi));
		v[i].push_back(Node(s, 0, ids));
	}
	for(int i = 1; i <= m; i++) {
		int idi = v[i + Max(n, m)].size();
		int idt = v[t].size();
		v[i + Max(n, m)].push_back(Node(t, 1, idt));
		v[t].push_back(Node(i + Max(n, m), 0, idi));
	}
}
int main() {
	Read();
	printf("%d", Dinic());
	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值