zoukankan      html  css  js  c++  java
  • [算法] 网络最大流 Dinic算法

    前言

    看到网上好多都用的链式前向星,就我在用 (vector) ……

    定义

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

    网络

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

    流量

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

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

    最大流

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

    增广路

    存在一条从 (S)(T) 的路径,使得路径上所有的流量都不为 (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)) ,其长度为 (C) ,建一条从 (A)(B) 的边,权值为 (C) ,与之相反的边权值为 (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));
    }
    

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

    主体部分

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

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

    先来看 (bfs) 。其返回值为 (bool) ,意为该残量网络中是否还存在增广路。层数 (de[i]) 的意义很明白: (S) 到达当前的点 (i) 的最小步数。而按照这样的分层,每次只能将当前流量信息传递到下一层数节点上,可以很大程度上避免张冠李戴的情况。若 (T) 的层数为 (1) ,则说明当前 (S) 不能通向 (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;
    }
    

    再来看 (dfs) ,来判断每一次的网络是否可以传递,完成增广的过程(以下代码附上注释)。这样一次走了不止 (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;
    }
    

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

    时间复杂度

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

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

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

    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;
    }
    
  • 相关阅读:
    第11组 Alpha冲刺(4/6)
    第11组 Alpha冲刺(3/6)
    第11组 Alpha冲刺(2/6)
    第11组 Alpha冲刺(1/6)
    团队Git现场编程实战
    第11组 团队项目-需求分析报告
    团队项目-选题报告
    第10组 Alpha冲刺(2/6)
    第10组 Alpha冲刺(1/6)
    2019 SDN上机第2次作业
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14083140.html
Copyright © 2011-2022 走看看