网络流

性质

对于任意一个时刻,设f(u,v)实际流量,则整个图G的流网络满足3个性质:

容量限制:对任意u,v∈V,f(u,v)≤c(u,v)。

反对称性:对任意u,v∈V,f(u,v) = -f(v,u)。从u到v的流量一定是从v到u的流量的相反值。

流守恒性:对任意u,若u不为S或T,一定有∑f(u,v)=0,(u,v)∈E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。

开端

最大流

从源点到汇点的最大流量

主要思路

从源点dfs还有剩余容量的点,如果搜到汇点,证明还有增广路,更新最大流,更新容量,建立反向边(先建立正反两条边,正边为容量,反边为零,dfs时减小正边流量,增加反向边容量)

优化

dinic使用bfs,将该边的剩余容量给到下一个边,以达到同时搜多条边

为防止搜回来,先使用bfs进行分层,每次搜索只能搜到下一层

代码实现
bool bfs(){//bfs分层,防止多条路同时搜索时搜回来 
	memset(dep,0,sizeof(dep));//初始化 
	q.push(root);
	dep[root]=1;//第几层 
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(e[i].val&&!dep[v]){//还有容量并且是下一层 
				dep[v]=dep[u]+1;
				q.push(e[i].to);
			}
		}
	}
	return dep[t];//判断是否有增广路,如果搜不到dep[t]说明没有增广路 
}

lt dfs(int u,lt in){
	if(u==t){
		return in;//如果搜到了汇点,就返回贡献值 
	}
	lt out=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(in<=0){
			break;
		}
		if(e[i].val/*还有容量*/&&dep[v]==dep[u]+1/*是下一层*/){
			lt res = dfs(v,min(in,e[i].val));
			e[i].val-=res;//减小该边容量 
			e[i^1].val += res;//反向建边; 
			in-=res;//该边的剩余容量减少,将剩余容量给到下一条边 
			out+=res;
		}
	} 
	if(out==0) {
		dep[u] = 0;//连不到t,没有继续搜下去的必要了 
		return 0;
	}
	return out;//返回增加的流量 
} 

再优化

当前弧优化:再每遍dfs时,记录cur数组,将当前点的head记录为当前的边(因为每一条边如果已经流满就没有在去搜的必要了)再次dfs到当前点时直接从cur记录的位置开始,每一次bfs时初始化cur数组,将其设置为节点的head值

代码实现 完整代码:
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3f
#define lt long long
using namespace std;

const int N=205,M=5005;
lt n,m,root,t;
lt ens;
lt tot=1;
lt head[N];
int cur[N]; 

struct edge{
	int nxt,to;
	lt val/*剩余容量*/;
	
}e[M*4];

void ade(int x,int y,lt z){
	e[++tot].to=y;
	e[tot].nxt=head[x];
	e[tot].val=z;
	head[x]=tot; 
}

lt dep[N];
queue<int> q;

bool bfs(){//bfs分层,防止多条路同时搜索时搜回来 
	memset(dep,0,sizeof(dep));//初始化 
	q.push(root);
	dep[root]=1;
	cur[root]=head[root];
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].to;
			if(e[i].val&&!dep[v]){//还有容量并且是下一层 
				cur[v]=head[v];
				dep[v]=dep[u]+1;
				q.push(e[i].to);
			}
		}
	}
	return dep[t];
}

lt dfs(int u,lt in){
	if(u==t){
		return in;//如果搜到了汇点,就返回贡献值 
	}
	lt out=0;
	for(int i=cur[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(in<=0){
			break;
		}
		if(e[i].val/*还有容量*/&&dep[v]==dep[u]+1/*是下一层*/){
			cur[u]=i;
			lt res = dfs(v,min(in,e[i].val));
			e[i].val-=res;
			e[i^1].val += res;//反向建边; 
			in-=res;
			out+=res;
		}
	} 
	if(out==0) {
		dep[u] = 0;//连不到t,没有继续搜下去的必要了 
		return 0;
	}
	return out;
} 

int main(){
	//freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    scanf("%lld%lld%lld%lld",&n,&m,&root,&t);
    for(int i=1;i<=m;i++){
    	int u,v;
    	lt val;
    	scanf("%d%d%lld",&u,&v,&val);
    	ade(u,v,val);
    	ade(v,u,0);//反向建边,但容量为0,待搜到后再增加容量 
    }
    while(bfs()){//仍有增广路 
    	ens+=dfs(root,inf);
    }
    printf("%lld\n",ens);
	return 0;
}

时间复杂度 (n^2* m);

发展

最小费用最大流

顾名思义就是在维护最大流的同时,使得花费最小。通过使用spfa来实现,用EK的思想,每次搜到一条价格最低的增广路,直到不能再找到增广路为止。

具体实现

用spfa走有剩余容量的边,对于每一个节点都维护新增流量(该次spfa中到他时的最大流量)、花费、进入的边、由哪个点到。每次spfa中,对于花费比他+到子节点的边的花费大的子节点用他来更新。每次看是否更新到汇点,如果没有更新到

(缓慢更新中……)

posted @ 2021-08-17 20:03  wangcongrui  阅读(78)  评论(0编辑  收藏  举报