网络流学习笔记
目录
-
网络流介绍
1.1 一些概念
1.2 网络流整体思路
-
EK 算法
-
dinic 算法
-
当前弧优化
-
求二分图最大匹配
-
费用流
1.网络流介绍
1.1 一些概念
网络流可以抽象为:你有一个自来水厂和很多输水管,和一个目标点,每一个输水管都有一个流量的限制。现在要将水从自来水厂运到输水管,求问最多能运多少水。
现在我们来介绍一些概念:
-
源点:就是前面的自来水厂,有无限的水
-
汇点:目标点
-
流量:从源点留到汇点的水量
-
增广路:每一条可以从源点留到汇点,且可以使流量增加的路径
-
残量网络:每一条边的边权减去包含这条边的增广路的流量所形成的图
1.2 网络流整体思路
每一次操作,我们找到一条增广路,记录它的流量加入到答案中,然后再在残量网络中找增广路,直到找不到为止。
但是假如我们选择了一条错误的增广路怎么办?
如下图:
这时我们发现,我们显然可以找到一条更好的,比如:
所以我们就要引入反悔操作,才能找到最大流量。
给每一条边建反边,反边初始流量为 0 ,每一次对一条增广路上的边减流量时,为它的反边加上这个流量。
最后还是不停找增广路就能找出最大流量了。
2. EK 算法
EK 算法就是,每次暴力广搜找增广路,直到残量网络中找不到增广路。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e2+5,M=5e4+5;
int head[N],tt,n,m,pre[N],s,t,cha,q[N],l,r,ans,fl[N][N];
bool f[N];
struct node {
int to,next,w,from;
} a[M];
void add(int x,int y,int z) {
a[++tt].to=y;
a[tt].from=x;
a[tt].w=z;
a[tt].next=head[x];
head[x]=tt;
}
bool bfs() {
l=1,r=0;
q[++r]=s;
memset(f,0,sizeof(f));
f[s]=1;
while(l<=r) {
int u=q[l];
l++;
for(int i=head[u]; i!=0; i=a[i].next) {
if(f[a[i].to]==1)
continue;
if(a[i].w==0)
continue;
q[++r]=a[i].to;
f[a[i].to]=1;
pre[a[i].to]=i;
if(a[i].to==t)
return 1;
}
}
return 0;
}
void EK() {
while(bfs()) {
cha=11451419198101926;
for(int i=pre[t]; i!=0; i=pre[a[i].from])
cha=min(cha,a[i].w);
ans+=cha;
for(int i=pre[t]; i!=0; i=pre[a[i].from])
a[i].w-=cha,a[i^1].w+=cha;
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>s>>t;
tt=1;
for(int i=1; i<=m; ++i) {
int u,v,w;
cin>>u>>v>>w;
if(fl[u][v]==0) {
add(u,v,w);
add(v,u,0);
fl[u][v]==t;
} else a[fl[u][v]-1].w+=w;
}
EK();
cout<<ans<<endl;
return 0;
}
3.dinic算法
我们发现,EK 暴力找增广路实在是太慢了。
所以我们可以使用 dinic 算法,先用 bfs 分层,然后同时增广,可以加快速度。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,s,t;
const int N=1e5+7;
const int inf=0x3f3f3f3f;
struct node
{
int to,nxt,w;
}e[N];
int head[N],cnt=1;
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=w;
head[u]=cnt;
}
int dep[N],vis[N];
queue<int> q;
int bfs()
{
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
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(!dep[v]&&e[i].w)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return dep[t];
}
int dinic(int x,int in)
{
int out=0;
if(x==t) return in;
for(int i=head[x];i&∈i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]==dep[x]+1&&e[i].w)
{
int val=dinic(v,min(in,e[i].w));
e[i].w-=val;
e[i^1].w+=val;
in-=val;
out+=val;
}
}
if(out==0) dep[x]=0;
return out;
}
signed main()
{
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,0);
}
int ans=0;
while(bfs())
ans+=dinic(s,inf);
cout<<ans;
return 0;
}
4.当前弧优化
我们可以在对于每个点记录下上一次扫到了哪一条边,然后下一次直接从这条边开始扫即可。
int dinic(int rt,int lim){
if(rt==T)
return lim;
int sum=0;
for(int &i=cur[rt];i!=0;i=a[i].next){
if(a[i].w==0||dep[a[i].to]!=dep[rt]+1)
continue;
int x=dinic(a[i].to,min(a[i].w,lim-sum));
sum+=x;
a[i].w-=x;
a[i^1].w+=x;
if(sum==lim)
return sum;
}
return sum;
}
void solve(){
while(bfs()){
for(int i=S;i!<=T;++i)
cur[i]=head[i];
ans+=dinic(S,inf);
}
}
5.求二分图最大匹配
二分图:节点由两个集合组成,且两个集合内部没有边的图。
匹配:选择一些边,使得每一条边的两个端点不在二分图的同一边。
如何用网络流算法求二分图最大匹配呢?
我们可以建超级源点与超级汇点,然后将超级源点与左边的点相连,流量为 inf ,将右边的点与超级汇点相连,流量也设置为 inf。
然后左右两两相连,流量为 1。
最后跑最大流即可。
6.费用流
关于费用流,其实就是求在最大流的时候的最小费用。
所以我们直接魔改 EK,在跑 bfs 的时候跑 spfa 即可。
#include<bits/stdc++.h>
#define int long long
#define endl "\n"
using namespace std;
const int N=5e5+7;
const int inf=1e18;
struct node
{
int to,nxt,f,c;
}e[N];
int head[N],cnt=1;
void add(int u,int v,int fl,int co)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].f=fl;
e[cnt].c=co;
head[u]=cnt;
}
int dis[N],vis[N],fl[N],pre[N],en[N];
queue<int> q;
int n,m,s,t;
int spfa(int s)
{
memset(dis,0x3f,sizeof(dis));
memset(fl,0x3f,sizeof(fl));
memset(vis,0,sizeof(vis));
q.push(s);
dis[s]=0;
pre[t]=-1;
vis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].c&&e[i].f)
{
dis[v]=dis[u]+e[i].c;
pre[v]=u;
en[v]=i;
fl[v]=min(e[i].f,fl[u]);
if(!vis[v])
{
q.push(v);
vis[v]=1;
}
}
}
}
return ~pre[t];
}
signed main()
{
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
add(a,b,c,d);
add(b,a,0,-d);
}
int ans1=0,ans2=0;
while(spfa(s))
{
int T=t;
ans1+=fl[t];
ans2+=(fl[t]*dis[t]);
while(T!=s)
{
e[en[T]].f-=fl[t];
e[en[T]^1].f+=fl[t];
T=pre[T];
}
}
cout<<ans1<<" "<<ans2;
return 0;
}