Loading

网络流学习笔记

目录

  1. 网络流介绍

    1.1 一些概念

    1.2 网络流整体思路

  2. EK 算法

  3. dinic 算法

  4. 当前弧优化

  5. 求二分图最大匹配

  6. 费用流

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&&in;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;
}
posted @ 2023-08-06 10:51  sheeplittlecloud  阅读(50)  评论(0编辑  收藏  举报