网络流和二分图初步

  • 杂谈

初步涉猎网络流,谈谈自己的理解,蒟蒻会不定期补充的 qwq

  • 网络流问题

  • 最大流问题: 

先举一个运输方案的例子 :

 可行方案:

 总运输量5百吨。 显而易见一个可行方案有如下性质:

而现在的问题转变为求Vs到Vt的最大运输量

概念概述:

  • EK算法

感性理解则为:增广的流量一定要符合短板效应,即等于路径上残量的最小值。

 EK算法具体实现:标程

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=105;
const int maxm=1005;
const int inf=0x7fffffff;
inline int read()
{
    int x=0,y=1; char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*y;
}
int n,m,s,t;
struct edge
{
    int u,to,c,next;
}g[maxm<<1];
int head[maxn],cnt(0);
inline void add(int a,int b,int c)
{
    g[cnt].u=a; g[cnt].to=b;
    g[cnt].c=c;
    g[cnt].next=head[a];
    head[a]=cnt++;
    return ;
}
int pre[maxn];
void bfs(int s,int t)
{
    memset(pre,-1,sizeof(pre));
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=g[i].next)
        {
            int v=g[i].to;
            if(pre[v]!=-1||!g[i].c) continue;
            pre[v]=i; 
            if(v==t) break;
            q.push(v);
        }
        if(pre[t]!=-1) break;
    }
    return ;
}
int ans(0);
int EK(int s,int t)
{
    ans=0;
    while(1)
    {
        bfs(s,t);
        if(pre[t]==-1) break;
        int mn=inf;
        for(int i=t;i^s;i=g[pre[i]].u)
            mn=min(mn,g[pre[i]].c);
        for(int i=t;i^s;i=g[pre[i]].u)
        {
            g[pre[i]].c-=mn;
            g[pre[i]^1].c+=mn;
        }
        ans+=mn;
    }
    return ans;
}
int main()
{
    n=read(); m=read();
    s=read(); t=read();
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        int a,b,v;
        a=read(); b=read();
        v=read();
        add(a,b,v); add(b,a,0);
    }
    printf("%d\n",EK(s,t));
    return 0;
}
/*4 4 1 4
1 2 5
1 3 1
2 4 2
3 4 3*/

话说网络流真令人不爽,head存图时候要初始-1,链式前向星存边编号从0开始

  • Dinic算法 

 dinic算法同EK一样,都是不断去找增广路的过程

板子如下: 

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=1205;
const int maxm=1.2e5+5;
const int inf=0x7fffffff;
#define int long long
inline int read()
{
	int x=0,y=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*y;
}
struct edge
{
	int u,to,next,c;
}g[maxm<<1];
int head[maxn],cnt(0);
int n,m,s,t;
inline void add(int a,int b,int c)
{
	g[cnt].to=b; g[cnt].u=a;
	g[cnt].c=c;
	g[cnt].next=head[a];
	head[a]=cnt++;
	return ;
}
int dep[maxn];
bool bfs(int s,int t)
{
	memset(dep,-1,sizeof(dep));
	queue<int> q;
	q.push(s); dep[s]=0;
	while(!q.empty())
	{
		int u=q.front(); q.pop();
		for(int i=head[u];i!=-1;i=g[i].next)
		{
			int v=g[i].to;
			if(dep[v]^-1||!g[i].c) continue;
			dep[v]=dep[u]+1;
			q.push(v);
		}
        //构建网络,进行分层
	}
	return (dep[t]!=-1);
}
int dfs(int u,int flow)
{
	if(u==t) return flow;
    //到达汇点flow可以作为最终流量
	int res=0;
	for(int i=head[u];i!=-1;i=g[i].next)
	{
		int v=g[i].to;
		if(dep[v]==dep[u]+1&&g[i].c)
		{
			int x=dfs(v,min(flow,g[i].c));
			flow-=x; res+=x;
			g[i].c-=x; g[i^1].c+=x;
			if(!flow) break;
            //余额用尽,不能增广了
		}
	}
	if(!res) dep[u]=-1; //炸点,即从这个点出发不可能增广
	return res; //返回的是该点增广的总流量
}
int dinic()
{
	int ans(0);
	while(bfs(s,t))
		ans+=dfs(s,inf);
	return ans;
}
signed main()
{
	n=read(); m=read();
	s=read(); t=read();
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int u,v,c;
		u=read(); v=read();
		c=read();
		add(u,v,c); add(v,u,0);
	}
	printf("%lld\n",dinic());
	return 0;
}

算法过程图在这里

  • 最大流最小割 

首先介绍什么是割,但一般指s-t割  

  • 最小费用最大流

这个问题有一个明确的方法,因为不论选择哪条增广路优先增广都能求出最大流,因此我们确保每次求增广路的bfs用spfa替代,就能保证一定求出最小费用的最大流,若求最大费用,就spfa求最长路。

 关于求法,我们只需将bfs替换后接上ek或者dinic即可

P3381 【模板】最小费用最大流https://www.luogu.com.cn/problem/P3381 板子题,上板子:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=3e5+5;
const int maxm=5e4+5;
const int inf=0x3f3f3f3f;
typedef pair<int,int> pii;
inline int read()
{
	int x=0,y=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*y;
}
int n,m,s,t;
struct edge
{
	int u,to,c;
	int w,next;
}g[maxm<<1];
int head[maxn],cnt(0);
void add(int a,int b,int c,int w)
{
	g[cnt].u=a,g[cnt].to=b;
	g[cnt].c=c; g[cnt].w=w;
	g[cnt].next=head[a];
	head[a]=cnt++;
	return ;
}
int dis[maxn];
bool vis[maxn];
int pre[maxn];
bool spfa(int s)
{
	memset(dis,0x3f,sizeof(dis));
	memset(vis,0,sizeof(vis)); 
	memset(pre,-1,sizeof(pre));
	queue<int> q;
	q.push(s);
	dis[s]=0; vis[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop(); vis[u]=0;
		for(int i=head[u];i!=-1;i=g[i].next)
		{
			if(!g[i].c) continue;
            //这里只需要确保是增广路即可,不用验pre[v]是否访问过
			int v=g[i].to;
			if(dis[v]>dis[u]+g[i].w)
			{
				dis[v]=dis[u]+g[i].w;
				pre[v]=i;
				if(!vis[v]) q.push(v),vis[v]=1;
			}
		}
	}
	return (pre[t]!=-1);
}
pii EK(int s,int t)
{
	int ans(0),res(0);
	pii kt;
	while(spfa(s))
	{
		int mn=inf;
		for(int i=t;i^s;i=g[pre[i]].u)
			mn=min(mn,g[pre[i]].c);
		for(int i=t;i^s;i=g[pre[i]].u)
		{
			g[pre[i]].c-=mn;
			g[pre[i]^1].c+=mn;
			ans+=mn*g[pre[i]].w;
		}
		res+=mn;
        //在这里改ans也行
        //ans+=mn*dis[t];
	}
	kt.first=res,kt.second=ans;
	return kt;
}
int main()
{
	n=read(); m=read();
	s=read(); t=read();
	memset(head,-1,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		int u,v,c,w;
		u=read(); v=read();
		c=read(); w=read();
		add(u,v,c,w);
		add(v,u,0,-w);
	}
	pii tmp=EK(s,t);
	printf("%d %d\n",tmp.first,tmp.second);
	return 0;
}
  • 二分图&匹配

 有点抽象,翻译成人话,如下图所示,极大匹配,意思是边数最多的匹配,完美匹配,意思是把所有点都用上的匹配,饱和点就是下图u,v里面的点的集合

 

  • 匈牙利算法

不断增广的过程,由于一次增广每个点只需要访问一次

代码如下:

upd:2023-3-26

二分图最大匹配的代码有问题

下面才是正确的

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=1e3+5;
const int maxm=1e6+5;
inline int read()
{
	int x=0,y=1; char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*y;
}
int n,m,e;
struct egde
{
	int to,next;
}g[maxm<<1];
int head[maxn],cnt(0);
inline void add(int a,int b)
{
	g[++cnt].to=b;
	g[cnt].next=head[a];
	head[a]=cnt;
	return ;
}
int rmatch[maxn];
bool vis[maxn];
bool dfs(int u)
{
    vis[u]=1;
	for(int i=head[u];i;i=g[i].next)
	{
		int v=g[i].to;
		if(vis[rmatch[v]]) continue;
		if(!rmatch[v]||dfs(rmatch[v]))
		{rmatch[v]=u; return 1;}
	}
	return false;
}
int km()
{
	int res=0;
	for(int i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		res+=(dfs(i)==1);
	}
	return res;
}
int main()
{
	n=read(); m=read(); e=read();
	for(int i=1;i<=e;i++)
	{
		int u,v;
		u=read(); v=read();
		if(u<=n&&v<=m) add(u,v);
	}
	printf("%d\n",km());
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值