网络流问题

概念

可以想象成一个运水系统:
从源点流出,通过管道运输,途径多个中转点,最后达到汇点

▪ 源点:无限的水
▪ 管道:运输的水量有限制
▪ 中转点:接收多少水,输出多少水,但是可以改变水流的方向
▪ 汇点:一般求可以接受的最大水量

如下面这个图:
在这里插入图片描述
最大流量是5:
1->3提供2流量
1->2->3提供2流量
1->2->4->3提供1流量

其中,1->2->3是一条增广路(从源点到汇点的路径,其上所有边的残余容量均大于0),扣除掉这条路提供的2流量之后,1->2的容量变成1,2->3的容量变成0,这时的容量称为残余容量,1->2->4->3也是一条增广路

EK算法

思想:不断用BFS寻找增广路并不断更新最大流量值,直到网络上不存在增广路为止

(FF算法是用DFS搜索,效率不高,一般不使用)
如果只是用BFS搜索增广路会出现一些问题:
在这里插入图片描述
如果先找到了1->2->3->4这条边,那么残余网络就会变成:
在这里插入图片描述
此时算出来的最大流是1,但其实这个图的最大流是走1->3->4和1->2->4,为2

所以可以通过引入反向边解决:

在建边的同时,在反方向建一条边权为0的边,当扣除正向边的流量的时候,对应的反向边要增加相等的流量

在这里插入图片描述
此时如果再选择1->2->3->4,正向边-1,反向边+1,得到
在这里插入图片描述
就找到另外一条增广路:1->3->2->4
此时,我们同时选择了2->3和3->2两条边,就可以认为,这两条边上的水流抵消了,
所以实际上选择的路径就是1->3->4和1->2->4

反向边在这里就类似一种撤销操作,给程序一种反悔的机会。
加入了反向边这种反悔机制后,我们就可以保证,当找不到增广路的时候,流到汇点的流量就是最大流

模板题:P3376 网络最大流
代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,s,t,u,v;
long long w,ans,dis[520010];
int tot=1,vis[520010],pre[520010],head[520010],flag[2510][2510];

struct node
{
    int to,net;
    long long val;
} e[520010];

inline void add(int u,int v,long long w)
{
    e[++tot].to=v;
    e[tot].val=w;
    e[tot].net=head[u];
    head[u]=tot;
    
    e[++tot].to=u;
    e[tot].val=0;
    e[tot].net=head[v];
    head[v]=tot;
}

int bfs()    //bfs寻找增广路
{
    for(register int i=1; i<=n; i++) vis[i]=0;
    queue<int> q;
    q.push(s);
    vis[s]=1;
    dis[s]=2005020600;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x]; i; i=e[i].net)
        {
            if(e[i].val==0) continue;  //我们只关心剩余流量>0的边
            int v=e[i].to;
            if(vis[v]==1) continue;  //这一条增广路没有访问过
            dis[v]=min(dis[x],e[i].val);
            pre[v]=i;  //记录前驱,方便修改边权
            q.push(v);
            vis[v]=1;
            if(v==t) return 1;  //找到了一条增广路
        }
    }
    return 0;
}

void update()    //更新所经过边的正向边权以及反向边权
{
    int x=t;
    while(x!=s)
    {
        int v=pre[x];
        e[v].val-=dis[t];
        e[v^1].val+=dis[t];
        x=e[v^1].to;
    }
    ans+=dis[t];   //累加每一条增广路经的最小流量值
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(register int i=1; i<=m; i++)
    {
        scanf("%d%d%lld",&u,&v,&w);
        if(flag[u][v]==0)    //处理重边的操作(加上这个模板题就可以用Ek算法过了)
        {
            add(u,v,w);
            flag[u][v]=tot;
        }
        else
        {
            e[flag[u][v]-1].val+=w;
        }
    }
    while(bfs()!=0)    //直到网络中不存在增广路
    {
        update();
    }
    printf("%lld",ans);
    return 0;
}

在这里插入图片描述

Dinic算法

EK每次遍历只找到一条增广路,Dinic可以遍历一次找到多条

思想:分层图+DFS

  • 分层
    使用bfs,用 l e v [ m a x x ] lev[maxx] lev[maxx]数组从原点开始标记每个结点的层次
  • dfs
    比如:
    在这里插入图片描述
    就可以从S开始,每次我们向下一层次随便找一个点,直到到达T,然后从T一层一层往上返回,每返回一层就遍历这一层的所有点

路径:
① s->1->4->t,此时回溯的时候发现1还有路可以走
② s->1->5->t ,③ s->1->3->t
然后回溯到s,发现还有路
④ s->2->3->t

优化

1.定义一个变量vis,表示是否能到达t,若能到达t则可以再次dfs,否则重新bfs

2.定义一个变量used,用来表示这个点的流量用了多少
这样,在dfs时,在找到一条增广路时,不直接返回,而是改变used的值,如果used还没达到该点流量上限fow,可以在这个点继续找别的增广路

当前边优化

在这里插入图片描述

代码:

#include <bits/stdc++.h>
#define maxx 10010
#define inf 0x3f3f3f3f
typedef long long ll;
using namespace std;

int n,m,s,t,u,v;
ll w,ans;
int cnt=1;
int head[maxx],lev[maxx],cur[maxx];
struct node
{
    int to,next;
    ll w;
} e[maxx*3];

void add(int from,int to,int w)
{
    e[++cnt].to=to;
    e[cnt].w=w;
    e[cnt].next=head[from];
    head[from]=cnt;

    e[++cnt].to=from;
    e[cnt].w=0;
    e[cnt].next=head[to];
    head[to]=cnt;
}
//在残量网络中构造分层图
int bfs()
{
    memset(lev,-1,sizeof lev);
    lev[s]=0;
    memcpy(cur,head,sizeof head);
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        for(int i=head[x]; i; i=e[i].next)
        {
            int to=e[i].to;
            int ww=e[i].w;
            if(ww>0 && lev[to]==-1) //残量>0,且没有被遍历过
            {
                lev[to]=lev[x]+1; //层次+1
                q.push(to);
            }
        }
    }
    return lev[t]!=-1; //汇点一定遍历到,遍历不到说明搜索结束
}

int dfs(int x,int flow)
{
    if(x==t)
        return flow;
    int rmn=flow;
    for(int i=cur[x]; i&&rmn; i=e[i].next)
    {
        cur[x]=i; //优化****************************************************
        int to=e[i].to;
        int w=e[i].w;
        if(w>0 && lev[to]==lev[x]+1) //往上一层增广
        {
            int c=dfs(to,min(w,rmn));// 尽可能多地传递流量,但必须在范围之内
            rmn-=c;
            e[i].w-=c; //更新残余容量
            e[i^1].w+=c; //最后一位取反
        }
    }
    return flow-rmn;
}
int main()
{
    cin>>n>>m>>s>>t;
    for(int i=0; i<m; i++)
    {
        cin>>u>>v>>w;
        add(u,v,w);
    }

    while(bfs())
    {
        ans+=dfs(s,inf);
    }
    cout<<ans<<endl;
    return 0;
}

在这里插入图片描述
(n为点数,m为边数)

ISAP算法

优化dinic,只跑一次bfs

参考这里

二分图匹配

最大流最小割

割:去掉一些边,把本来的图分为不连通两部分,代价最小,割的大小是这些边的容量之和
最大流等于最小割

最小费用最大流

Minimum Cost Maximum Flow,MCMF
现在的网络上增加了一个属性:单位费用,一条边上的费用=流量×单位费用。
我们知道,网络最大流往往可以用多种不同的方式达到,所以现在要求:在保持流最大的同时,找到总费用最少的一种。
在这里插入图片描述

思路:每次增广费用最少的一条路径,即,把EK算法里的BFS换成SPFA

SPFA:

  1. 只让当前点能到达的点入队
  2. 如果一个点已经在队列里,便不重复入队
  3. 如果一条边未被更新,那么它的终点不入队

题目

1.餐巾纸

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值