首页 > 其他分享 >网络流学习笔记

网络流学习笔记

时间:2023-08-06 11:00:15浏览次数:34  
标签:head 增广 int 网络 笔记 学习 dep cnt fl

目录

  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;
}

标签:head,增广,int,网络,笔记,学习,dep,cnt,fl
From: https://www.cnblogs.com/sheeplittlecloud/p/17609170.html

相关文章