网络流算法//最大流(洛谷3376)//EK算法//dinic算法//最小(大)费用最大流(待续)

目录

最大流流算法

(EK算法)

时间复杂度 O(V*(E^2))

 

Dinic算法

时间复杂度O((V ^ 2) * E)


在洛谷题解中看到了一句很有启发的话:网络流善于解决各种有要求的匹配

联想到题目是匹配问题,且满足网络流要求的数据范围,可以尝试网络流

 

V是点的数目,E是边的数目

最大流流算法

(EK算法)

时间复杂度 O(V*(E^2))

定义

我们有一个 源点(只有流出量) 和 汇点(只有流入量)

每条边有一个 流量 和 容量,流量最初为0,容量是题目告知我们的,我们要求的是 在所有边的 流量 <= 容量 && 每个点流入量 = 流出量 时,流入汇点的最大流是多少。


首先我们需要知道残留网络

残留网络就是反向图的流量,我们正向遍历一个图,会走一条路径到汇点。

一条路径的   增广路 = min(经过的每条边的 {容量 - 当前流量} )

但是我们这条边可能是反向的流,所以

  • 正向边的容量 -= 增广路
  • 反向边容量 += 增广路

找到增广路,我们就 flow += 增广路

当找不到增广路就结束了这个算法。

因为看了vector建图和链式前向星建图的效率和空间差异,就手动改了一下模板,不用常用的mp[][] 数组标记,练习一下链式前向星。不太懂的,这里看看https://blog.csdn.net/castomere/article/details/80426485

 

最大流最小割定理:

割 是 一些边被砍断,让S(源点) 和 T(汇点)不连通的边,这些边的容量sum就是 割 的容量,最小割,就是让割的容量最小,想像最大流时,一些边被流填满,那么 最大流 就是 最小割的容量

 

洛谷3376

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;
// O{V * E ^ 2}
/*
源点 st , 汇点 ed
*/
int N , M;
int st , ed;

int cnt;
struct edge{
	int Next;//下一条边的存储下标
	int to;//这条边的终点
	int w;//边权
};
edge Edge[MaxN];
int head[Maxn];//以 i 为起点的第一条边的标记
void AddEdge(int u , int v , int w){
	Edge[++cnt].Next = head[u];
	Edge[cnt].w = w;
	Edge[cnt].to = v;
	head[u] = cnt;
}

int pre[Maxn][2];
int Flow;
int flow[Maxn];
queue <int> que;
bool Bfs(){
	while(!que.empty())	que.pop();
	for(int i = 0 ; i <= N ; i++) pre[i][0] = pre[i][1] = -1;
	flow[st] = Inf;
	int q;
	que.push(st);
	while(!que.empty()){
		q = que.front(); que.pop();
		if(q == ed)	break;
		for(int i = head[q] ; ~i ; i = Edge[i].Next){
			int nq = Edge[i].to;
			if(nq != st && Edge[i].w && pre[nq][0] == -1){//下个点nq不是st,且边权>0,nq没被遍历过
				pre[nq][0] = i;//记录前一条边的编号
				pre[nq][1] = q;//记录nq前一个点q
				flow[nq] = min(Edge[i].w , flow[q]);//到nq路径上取min边权
				que.push(nq);
			}
		}
	}
	if(pre[ed][0] == -1)	return false;
	return true;
}

void MaxFlow(){
	int add , pl , fa , p;
	while(Bfs()){
		add = flow[ed];
		p = ed;
		while(p != st){
			pl = pre[p][0];//p上一条边的编号
			p = pre[p][1];//p变为它的上一个点
			Edge[pl].w -= add;
			Edge[pl^1].w += add;
		}
		Flow += add;
	}
	printf("%d\n",Flow);
	return;
}

void init(){
	for(int i = 0 ; i <= N ; i++)	head[i] = -1;
	cnt = -1;//从-1 开始 可以用 x ^ 1 来关联正反边
	return;
}

int main()
{
	scanf(" %d %d %d %d",&N,&M,&st,&ed);
	init();
	for(int i = 1 ; i <= M ; i++){
		int u , v , c , ct1 , ct2;	scanf(" %d %d %d",&u,&v,&c);
		AddEdge(u , v , c);
		AddEdge(v , u , 0);
	}
	MaxFlow();
	return 0;
}

 

Dinic算法

EK通常解决10^3 –10^4规模的网络 
而dinic能解决10^4–10^5的网络

时间复杂度O((V ^ 2) * E)

(真的快了不是一点,这一道题,Dinic是EK的 1/4 时间)

算法流程:

  • 在存在增广路的边上建立分层图,如果汇点不在存在增广路的分层图上,那么就不存在新的增广路,结束算法
  • 当存在汇点增广路,执行Dfs操作,在分层图中寻找增广路,增广路径从 u 到 v , 当且仅当dep[v] = dep[u] + 1
  • 找到增广路,Dfs回溯时减去增广路,一个节点可能减了多次增广路。

洛谷3376

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;

int N , M;
int st , ed;
int Flow;

int cnt;
struct edge{
	int Next;
	int to;
	int w;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w){
	Edge[++cnt].Next = head[u];
	Edge[cnt].w = w;
	Edge[cnt].to = v;
	head[u] = cnt;
}

int dep[Maxn];
queue <int> que;
bool Bfs(){
	for(int i = 1 ; i <= N ; ++i)	dep[i] = 0;
	while(!que.empty())	que.pop();
	dep[st] = 1;
	que.push(st);
	int q , v;
	while(!que.empty()){
		q = que.front(); que.pop();
		for(int i = head[q] ; ~i ; i = Edge[i].Next){
			v = Edge[i].to;
			if(Edge[i].w && !dep[v]){
				que.push(v);
				dep[v] = dep[q] + 1;
				if(v == ed)	return true;
			}
		}
	}
	return false;
}

int Dfs(int x , int flow){
	if(x == ed)	return flow;
	int res = flow , add;
	for(int i = head[x] ; ~i && res ; i = Edge[i].Next){
		int v = Edge[i].to;
		if(Edge[i].w && dep[v] == dep[x] + 1){
			add = Dfs(v , min(res , Edge[i].w));
			if(!add)	dep[v] = 0;
			Edge[i].w -= add;
			Edge[i^1].w += add;
			res -= add;
		}
	}
	return flow - res;
}

void Dinic(){
	int flow;
	while(Bfs()){
		while(flow = Dfs(st , Inf)){
			Flow += flow;
		}
	}
	printf("%d\n",Flow);
}
void init(){
	for(int i = 1 ; i <= N ; i++)	head[i] = -1;
	Flow = 0;
	cnt = -1;
}


int main()
{
	scanf(" %d %d %d %d",&N,&M,&st,&ed);
	init();
	for(int i = 1 ; i <= M ; i++){
		int u , v , c; scanf(" %d %d %d",&u,&v,&c);
		AddEdge(u , v , c);
		AddEdge(v , u , 0);
	}
	Dinic();
}

 

洛谷 P2763


我们已经学会了基础的最大流,那么就需要继续研究最小费用最大流 和 最大费用最大流

最小费用最大流

定义:

在最大流相同的情况下,我们要求花费最小的费用。

我们发现它类似最短路的条件,我们把价格所以就在求解增广路的过程中加一个 条件来筛选,即选择最短路进行增广。

分析:

因为dijkstra是在一个点的连边中 选最小的边权进行拓展,就像它不能处理带负权的最短路一样,所以我们用SPFA维护 反向边的负权流量。其他的同步最大流算法。

洛谷P3381

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;

int N , M;
int st , ed;
int Flow , MinCost;

int cnt;
struct edge{
	int Next;
	int to;
	int w;
	int c;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w , int cost){
	Edge[++cnt].Next = head[u];
	Edge[cnt].w = w;
	Edge[cnt].to = v;
	Edge[cnt].c = cost;
	head[u] = cnt;
}

int dis[Maxn] , pre[Maxn] , last[Maxn] ,  flow[Maxn];
bool vis[Maxn];
//last[i] 前一条边 , pre[i] 前一个点
queue <int> que;
bool Spfa(){
	for(int i = 1 ; i <= N ; ++i){
		dis[i] = flow[i] = Inf;
		vis[i] = false;
	}
	int q , v , w , cost;
	pre[ed] = -1;
	vis[st] = 1; dis[st] = 0;
	que.push(st);
	while(!que.empty()){
		q = que.front(); que.pop();
		vis[q] = 0;
		for(int i = head[q] ; ~i ; i = Edge[i].Next){
			v = Edge[i].to;
			w = Edge[i].w;
			cost = Edge[i].c;
			if(w > 0 && dis[v] > dis[q] + cost){
				dis[v] = dis[q] + cost;
				last[v] = i;
				pre[v] = q;
				flow[v] = min(flow[q] , w);
				if(!vis[v]){
					vis[v] = 1;
					que.push(v);
				}
			}
		}
	}
	if(pre[ed] != -1)	return true;
	return false;
}

void MinCostFlow(){
	while(Spfa()){
		int p = ed;
		Flow += flow[ed];
		MinCost += flow[ed] * dis[ed];
		while(p != st){
			Edge[last[p]].w -= flow[ed];
			Edge[last[p]^1].w += flow[ed];
			p = pre[p];
		}
	}
	printf("%d %d\n",Flow , MinCost);
}

void init(){
	for(int i = 0 ; i <= N ; i++){
		head[i] = -1;
	}
	cnt = -1;
	Flow = MinCost = 0;
}

int main()
{
	scanf(" %d %d %d %d",&N ,&M ,&st ,&ed);
	init();
	for(int i = 1 ; i <= M ; i++){
		int u , v , w , cost; scanf(" %d %d %d %d",&u,&v,&w,&cost);
		AddEdge(u , v , w , cost);
		AddEdge(v , u , 0 , -cost);
	}
	MinCostFlow();
}

 

最大费用最大流

 

先贴个板子

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <iostream>
#include <cmath>
#include <map>
#include <queue>
#include <algorithm>
#include <set>
#include <vector>
#include <stack>
#define Clear( x , y ) memset( x , y , sizeof(x) );
#define Qcin() std::ios::sync_with_stdio(false);
using namespace std;
typedef long long LL;
const int Maxn = 1e4 + 7;
const int MaxN = 2e5 + 7;
const int Inf = 1e9 + 7;

int N , M;
int st , ed;
int Flow , MaxCost;

int cnt;
struct edge{
	int pre;
	int Next;
	int to;
	int w;
	int c;
};
edge Edge[MaxN];
int head[Maxn];
void AddEdge(int u , int v , int w , int cost){
	Edge[++cnt].Next = head[u];
	Edge[cnt].pre = u;
	Edge[cnt].w = w;
	Edge[cnt].to = v;
	Edge[cnt].c = cost;
	head[u] = cnt;
}

int dis[Maxn] , preWay[Maxn] ,  flow[Maxn];
bool vis[Maxn];
//last[i] 前一条边 , pre[i] 前一个点
queue <int> que;
bool Spfa(){
	for(int i = 1 ; i <= N ; ++i){
		dis[i] = -Inf;
		preWay[i] = -1;
		vis[i] = false;
	}
	int q , v , w , cost;
	vis[st] = 1; dis[st] = 0;
	que.push(st);
	while(!que.empty()){
		q = que.front(); que.pop();
		vis[q] = 0;
		for(int i = head[q] ; ~i ; i = Edge[i].Next){
			v = Edge[i].to;
			w = Edge[i].w;
			cost = Edge[i].c;
			if(w > 0 && dis[v] < dis[q] + cost){
				dis[v] = dis[q] + cost;
				preWay[v] = i;
				if(!vis[v]){
					vis[v] = 1;
					que.push(v);
				}
			}
		}
	}
	if(preWay[ed] != -1)	return true;
	return false;
}

void MaxCostFlow(){
	while(Spfa()){
		int tmpFlow = Inf;
		for(int i = preWay[ed] ; ~i ; i = preWay[Edge[i].pre] ){
			if(tmpFlow > Edge[i].w){
				tmpFlow = Edge[i].w;
			}
		}
		for(int i = preWay[ed] ; ~i ; i = preWay[Edge[i].pre]){
			Edge[i].w -= tmpFlow;
			Edge[i^1].w += tmpFlow;
			MaxCost += tmpFlow * Edge[i].c;
		}
		Flow += tmpFlow;
	}
	printf("%d %d\n",Flow , MaxCost);
}

void init(){
	for(int i = 0 ; i <= N ; i++){
		head[i] = -1;
	}
	cnt = -1;
	Flow = MaxCost = 0;
}

int main()
{
	scanf(" %d %d %d %d",&N ,&M ,&st ,&ed);
	init();
	for(int i = 1 ; i <= M ; i++){
		int u , v , w , cost; scanf(" %d %d %d %d",&u,&v,&w,&cost);
		AddEdge(u , v , w , cost);
		AddEdge(v , u , 0 , -cost);
	}
	MaxCostFlow();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值