网络流学习笔记

网络流写的还是太少啦,可能没时间写更多的题了
主要是今天的测试网络流建模建错了,气的我写了这篇博客
可能还是因为见过的题目太少吧

最大流

这里用到的是一个残量网络的概念,我们每次找一条可以被更新的路径,减去路径上的最小容量
记得反向边增加流量,可以起到一个反悔的作用
这就是EK算法

#include<queue>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100007;
const int INF = 2147483647;
struct node{
	int to,next,flow;
}edge[maxn*3];
int cnt=-1,head[maxn];
int last[maxn],pre[maxn],d[maxn],g[maxn];
int vis[maxn];
void add(int from,int to,int flow){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	edge[cnt].flow=flow;// 流量
	head[from]=cnt;
}

int n,m;

bool EK_spfa(int s,int t){
	for(int i=1;i<=n;i++)g[i]=INF;
	memset(vis,0,sizeof(vis));
	vis[s]=1;
	pre[t]=-1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];i!=-1;i=edge[i].next){
			int to=edge[i].to;
			if(edge[i].flow&&!vis[to]){
				pre[to]=u;
				last[to]=i;
				g[to]=min(g[u],edge[i].flow);
				if(!vis[to])vis[to]=1,q.push(to);
			}
		}
	}
	return pre[t]!=-1;// 能不能跑到终点
}
int ans,cost;
void MFMC(int s,int t){
	while(EK_spfa(s,t)){
		int now=t;
		ans+=g[t];
		while(now!=s){// 增广路上的流量变化
			edge[last[now]].flow-=g[t];
			edge[last[now]^1].flow+=g[t];// 可以反悔
			now=pre[now];
		}
	}
}


int main(){
	memset(head,-1,sizeof(head));
	int s,t;
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int x,y,z,l;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z);
		add(y,x,0);//  反向0边可以反悔
	}
	MFMC(s,t);
	cout<<ans<<endl;
}

更好一点有dinic算法,这次我们使用了dfs,本质上还是找增广路径,那是如何做到优化的呢
我们每次找的不止一条增广路径,可以多路增广,并且神奇的当前弧优化还可以减少无用的路径枚举
那么如何做到多路增广不死循环呢,我们对图按照枚举顺序进行分层,严格按照层数dfs下去,就可以了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#define INF 2147483647
using namespace std;
const int maxn = 10010;
typedef long long ll;
struct node{
	ll to;
	ll next;
	ll flow;
}edge[maxn*20];
ll cnt=-1;
ll head[maxn];
ll cur[maxn];

void add(ll from,ll to,ll w){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	head[from]=cnt;
	edge[cnt].flow=w;
}

ll s,t,n,m,d[maxn];
queue<ll>q;

bool bfs(){
	ll used[maxn]={0};
	q.push(s);
	d[s]=1;
	used[s]=1;
	while(!q.empty()){
		ll f1=q.front();
		q.pop();
		for(int i=head[f1];i!=-1;i=edge[i].next){
			ll to=edge[i].to;
			ll f=edge[i].flow;
			if(!used[to]&&f>0){
				used[to]=1;
				d[to]=d[f1]+1;//  分层
				q.push(to);
			}
		}
	}
	return used[t];
}

ll dfs(ll x,ll a){
	ll f,ret=0;
	if(x==t||a==0)return a;
	else{
		for(ll &i=cur[x];i!=-1;i=edge[i].next){// 修改下次枚举的开头,可以直接从
		                                       //没有枚举过的边开始
			node &e=edge[i];
			if(d[x]+1==d[e.to]&&(f=dfs(e.to,min(a,e.flow)))>0){// 严格的层数限制
				e.flow-=f;
				edge[i^1].flow+=f;
				ret+=f;
				a-=f;
				if(a==0)return ret;// 从该点增广到无法增广为止
			}
		}
	}
	return ret;// 流量没用完,返回用过的值
}
ll flow;
void Max(){
	while(bfs()){
		for(int i=1;i<=n;i++)// 当前弧归零
			cur[i]=head[i];
		flow+=dfs(s,INF);
	}
	cout<<flow;
}
int main(){
	memset(head,-1,sizeof(head));
	cin>>n>>m>>s>>t;
	for(int i=1;i<=m;i++){
		ll x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,0);
	}
	Max();
	return 0;
}

因为笔者过菜,所以好像印象中非模板最大流就写过一个最小割…
洛谷P1345 [USACO5.4]奶牛的电信Telecowmunication

这题要求求最小的割点数
我们思考一下增广路停止的条件,是图不连通,似乎正好满足题意
那么我们思考如何让流量的使用达到题目的要求
我们拆点,把每一个点 i i i变成 i i i i + n i+n i+n,中间连一条边流量为1,这样,如果走了这一条边,就说明这个点被删了,但最后残量网络不连通的时候,就达到了我们的要求
但原图中的边怎么办呢
假设现在要从原图中添加一条从x到y的边到网络中去,对于点y来说,这条边的加入不应该影响通过它的流量限制(就是前面连的那个1)发生变化,所以前面那条y到y+n的边应该接在这条边的后面,所以这条边的终点连向网络中的y,相反的,这条边应该受到x的(前面连的1)限制。因为,假设x已经被删(x到x+n满流),那么这条边再加不加都是没有变化的。所以,这条边在网络中的起点应该是x+n,这样才保证受到限制。
建边完事,就是裸的最大流板子了,dinicEK随你了

补充一个二分图匹配的写法,对于给定的二分,我们首先推荐匈牙利算法滑稽
网络流当然可以 ,我们在合法的路径之间建边,流量为无限,建一个超级源和一个超级汇
超级源连接要匹配的点,流量为1,被匹配的点连超级汇,流量也为1,直接最大流就好了
这里笔者当时遇到了一个问题,我很傻的把图建成了连线之间流量为1,超级点之间为无限,结果肯定是错的,为什么呢,因为没有达到要求每个匹配点只能提供一点流量的这个要求,那么没什么好说的了,例题是洛谷P2756 飞行员配对方案问题

费用流

裸的费用流使用spfa,求最最大增广流量的时候顺带求最短路就行了

#include<queue>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 50007;
const int INF = 2147483647;
struct node{
	int to,next,w,flow;
}edge[maxn*3];
int cnt=-1,head[maxn];
int last[maxn],pre[maxn],d[maxn],g[maxn];
int vis[maxn];
void add(int from,int to,int w,int flow){
	edge[++cnt].to=to;
	edge[cnt].next=head[from];
	edge[cnt].flow=flow;
	edge[cnt].w=w;
	head[from]=cnt;
}

int n,m;

bool EK_spfa(int s,int t){
	for(int i=1;i<=n;i++)d[i]=INF,g[i]=INF;
	memset(vis,0,sizeof(vis));	
	vis[s]=1;
	d[s]=0;
	pre[t]=-1;
	queue<int>q;
	q.push(s);
	while(!q.empty()){
		int u=q.front();q.pop();
		vis[u]=0;
		for(int i=head[u];i!=-1;i=edge[i].next){
			int to=edge[i].to;
			if(edge[i].flow&&d[to]>d[u]+edge[i].w){
				d[to]=d[u]+edge[i].w;
				pre[to]=u;
				last[to]=i;
				g[to]=min(g[u],edge[i].flow);
				if(!vis[to])vis[to]=1,q.push(to);
			}
		}
	}
	return pre[t]!=-1;
}
int ans,cost;
void MFMC(int s,int t){
	while(EK_spfa(s,t)){
		int now=t;
		//cout<<"2";
		ans+=g[t];
		cost+=g[t]*d[t];
		while(now!=s){
			edge[last[now]].flow-=g[t];
			edge[last[now]^1].flow+=g[t];
			now=pre[now];
		}
	}
}


int main(){
	memset(head,-1,sizeof(head));
	int s,t;
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++){
		int x,y,z,l;
		scanf("%d%d%d%d",&x,&y,&z,&l);
		add(x,y,l,z);
		add(y,x,-l,0);
	}
	MFMC(s,t);
	cout<<ans<<" "<<cost<<endl;
}

费用流的建模就比较多了,虽然我也没见过几个你妈的
各种版本的类均分纸牌
P2512 [HAOI2008]糖果传递
P4016 负载平衡问题
因为几乎就一模一样所以就讲一个
我们要求的是最小费用暗示费用流,既然都是网络流建模了,那么怎么建边达到目的呢
对于这种最终通过操作移动达到目标状态的题目,大部分都是贡献的点连接源点,需要的点连向汇点
这题也不例外,我们新建一个超级源点,一个超级汇点,对于超过平均值的点,我们连接源点,流量为超过值,费用为0,反之,小于平均值的点,我们连接汇点,流量为,差值,费用也为零
这两种边是规定最大流量的
真正转移边是两点之间,流量为无穷,费用为1,这样,达到最大流的时候,一定是所有边跑满,此时求出的最小费用就是我们要的
没代码

下面的是各种版本的传纸条
P4066 [SHOI2003]吃豆豆
P1004 方格取数
P1006 传纸条
这里就讲传纸条思路

我们把每个点拆成入和出,入向出连一条容量为1,费用为该点权值的边,这代表第一次经过
再连一条容量为INF,费用0的边,这是备用的,表示经过一次之后再经过这个点不重复选
(因为原边跑过一次后流量归零消失了)
每个点的出向右和下的入连一条容量为INF费用为0的边,达到继承的目的
超级源连(1,1)的入一条容量为2,费用0的边,表示可以出去两股
点(n,m)的出连向超级汇,容量INF,费用0;
最终的最大费用最大流就是我们要的答案
still没代码
这里先停一停吧,最近再补一点网络流的题目再继续写
2019.9.9 0:13

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值