网络流学习笔记

· · 个人记录

概念

最大流: 在一个网络图上,每个边有流量限制,假如起始点有无线流量,求最多能有多少流量流到终点。

增广路: 一条从起始点到终点了路径,可以流流量。

算法

Ford-Fulkerson算法

解决这个问题,可以用Ford-Fulkerson算法。

该算法的核心就是寻找增广路。每找到一条增广路,就给它它能承受的最多流量。

我们注意到这样是有问题的,很可能最终结果不是最优的。因为先找到的增广路可能导致超过一条增广路断掉,最终答案劣。所以考虑反悔贪心。

反悔操作可以用反向边完成。建立反向边,每次增广路把正向边的流量给到反向边。这样一旦选到反向边,就相当于撤销了一次对边的选择操作,并把两次操作合并。可以证明这是最优的。

dfs爆搜即可。

Dinic算法

Dinic是针对FF的一种优化。

先使用bfs为所有点分层,然后每次dfs爆搜,只需要走层数较高的点,避免搜重和环。

弧优化

基于链式前向星,剪掉无用的dfs过程。分层后因为向回搜索不优,剪掉。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#define int long long
#define TOP st
using namespace std;
int tot=1,head[100004],n,m,st,ed,now[100004],tppo;
struct node
{
    int to,nex,w;
}e[100004],E[100004];
int ds[100004],nw[100004];
void add(int u,int v,int w)
{
//  cout<<u<<" "<<v<<' '<<w<<endl;
    e[++tot].to=v;
    e[tot].w=w;
    e[tot].nex=head[u];
    head[u]=tot;

    e[++tot].to=u;
    e[tot].w=0;
    e[tot].nex=head[v];
    head[v]=tot;
}
bool bfs()
{
    for(int i=1;i<=TOP;i++)
    {
        ds[i]=10000000000;
    }
    queue<int> q;
    q.push(st);
    ds[st]=0;
    now[st]=head[st];
    bool ret=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=E[i].nex)
        {
            int v=E[i].to;
            if(E[i].w>0&&ds[v]==10000000000)
            {
                q.push(v);
                now[v]=head[v];
                ds[v]=ds[u]+1;
            }
        }
    }
    return ds[ed]!=10000000000;
}
int dfs(int u,int sum)
{
    if(u==ed) return sum;
    int re=0,las;
    for(int i=now[u];i&&sum;i=E[i].nex)
    {
        now[u]=i;
        int v=E[i].to;
        if(E[i].w>0&&ds[v]==ds[u]+1)
        {
            las=dfs(v,min(sum,E[i].w));
            if(las==0) ds[v]=10000000000;
            E[i].w-=las;
            E[i^1].w+=las;
            re+=las;
            sum-=las;
        }
    }
    return re;
}
int Gans()
{
    memcpy(E,e,sizeof(e));
    memset(now,0,sizeof(now));
    memset(ds,0,sizeof(ds));
    int ans=0;
    while(bfs())
    {
        ans+=dfs(st,10000000000);
    }
    return ans;
}
signed main()
{
    int ans=0;
    scanf("%lld%lld%lld%lld",&n,&m,&st,&ed);
    for(int i=1,u,v,w;i<=m;i++)
    {
        scanf("%lld%lld%lld",&u,&v,&w);
        add(u,v,w);
    }
    cout<<Gans();
}

最小割

两点之间的割表示删除一些边使得两点不联通,这些边的边权和。

最小割指最小的割。

有结论:最大流=最小割

首先,割掉所有满流的边。若没有增广路,则此时两点必不联通,而流量恰巧就是所有流满边的边权之和,否则一定存在更大流。

一般用途在多个条件选一个条件满足,每个条件有代价的题目里面。

找到一组最小割,你需要从源点 dfs 只走非 0 边找到源点所在连通块。所有一端在连通块内一端不在的是一组割。

费用流

每个边流流量要费用,求在最大流的情况下最小费用。

由于增广顺序不影响,所以每次寻找最小费用增广路即可。

把 bfs 改成 SPFA(有负权边)搜索分层即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<queue>
#include<vector>
#define int long long
using namespace std;
int tot=1,head[1000004],n,m,st,ed,now[1000004],tppo;
struct node
{
    int to,nex,w,co;
}e[1000004],E[1000004];
int ds[1000004],nx[1000004],wnx[1000004];
bool inq[1000004];
void add(int u,int v,int w,int co)
{
//  cout<<u<<" "<<v<<' '<<w<<endl;
    e[++tot].to=v;
    e[tot].w=w;
    e[tot].co=co;
    e[tot].nex=head[u];
    head[u]=tot;

    e[++tot].to=u;
    e[tot].w=0;
    e[tot].nex=head[v];
    head[v]=tot;
    e[tot].co=-co;
}
bool bfs()
{
    int las=ds[ed];
    for(int i=0;i<=n;i++)
    {
        ds[i]=1000000000000000;
        inq[i]=false;
        wnx[i]=0;
        nx[i]=0;
    }
    queue<int> q;
    q.push(st);
    ds[st]=0;
    inq[st]=true;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        inq[u]=false;
        for(int i=head[u];i;i=E[i].nex)
        {
            int v=E[i].to;
            if(E[i].w>0&&ds[v]>ds[u]+E[i].co)
            {
                if(!inq[v])
                {
                    inq[v]=true;
                    q.push(v);
                }
                nx[v]=u;
                wnx[v]=i;
                ds[v]=ds[u]+E[i].co; 
            }
        }
    }
    return ds[ed]!=1000000000000000;
}
pair<int,int> Gans()
{
    memcpy(E,e,sizeof(e));
    memset(now,0,sizeof(now));
    memset(ds,0,sizeof(ds));
    int ans=0,rans=0;
    while(bfs())
    {
        int nw=ed,ll=1000000000000000;
        while(nw!=st)
        {
            ll=min(ll,E[wnx[nw]].w);
            nw=nx[nw];
        }
        nw=ed;
        while(nw!=st)
        {
            E[wnx[nw]].w-=ll;
            E[wnx[nw]^1].w+=ll;
            rans+=E[wnx[nw]].co*ll;
            nw=E[wnx[nw]^1].to;
        }
        ans+=ll;
    }
    return make_pair(ans,rans);
}
signed main()
{
    int ans=0;
    scanf("%lld%lld%lld%lld",&n,&m,&st,&ed);
    for(int i=1,u,v,w,co;i<=m;i++)
    {
        scanf("%lld%lld%lld%lld",&u,&v,&w,&co);
        add(u,v,w,co);
    }
    pair <int,int> kans=Gans();
    cout<<kans.first<<' '<<kans.second;
}

上下界网络流

无源汇可行流

判断是否有可行流使得满足流量上下界且平衡。

首先每个点先流下界。此时网络不平衡。

给一些边增加流量。新建一个网络流。

此时每个边可以增加的流量是上界减下界,所以每条边边权设为上界减下界。新增源点汇点。然后如果一条边需要流量,那么他向源点需要的流量,否则多余流量,向汇点连多余的流量。

对这个网络流跑最大流。判断是否所有点都平衡了,这相当于所有连向源点汇点的边满流。此时每个边的流量就是下界加当前对应边的流量。不满足就没有。

有源汇可行流

多了源点汇点,其实可以在源点汇点之间建立下界 0 上界无限的边,转化为无源汇的可行流。这条特殊边的流量就是可行流的流量。

有源汇最大流

首先解决可行流。

然后每条边减去可行流,相当于没有下界。

直接跑最大流再加起来即可。

相当于跑完残余流量。

有源汇最小流

首先解决可行流。

然后每条边变成可行流和下界的差。

直接跑最大流再减掉即可。

相当于去除多余流量。