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;
    }
    
  • 相关阅读:
    The Quad
    将OrCAD Capture CIS的设计文件(.dsn)导入到PADS Logic VX.2.3
    OrCAD Capture CIS 16.6 将版本16.6的设计文件另存为版本16.2的设计文件
    Eclipse IDE 添加jar包到Java工程中
    PADS Logic VX.2.3 修改软件界面语言
    切换Allegro PCB Editor
    Allegro PCB Design GXL (legacy) 将brd文件另存为低版本文件
    Allegro PCB Design GXL (legacy) 设置自动保存brd文件
    Could not create an acl object: Role '16'
    windows 下apache开启FastCGI
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14083140.html
Copyright © 2011-2022 走看看