网络最大流&最小割&最小费用最大流概述

本文详细介绍了网络最大流算法,包括基本概念、实现方法及其在解决实际问题中的应用。通过具体实例解析了最大流、最小割及最小费用最大流等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网络最大流:

P3376 【模板】网络最大流

题目描述

如题,给出一个网络图,以及其源点和汇点,求出其网络最大流。

输入格式:

第一行包含四个正整数 NMST N 、 M 、 S 、 T ,分别表示点的个数、有向边的个数、源点序号、汇点序号。

接下来 M M 行每行包含三个正整数uiviwi,表示第 i i 条有向边从ui出发,到达 vi v i ,边权为 wi w i (即该边最大流量为 wi w i

输出格式:

一行,包含一个正整数,即为该网络的最大流。

输入样例#1

4 5 4 3
4 2 30
4 3 20
2 3 20
2 1 30
1 3 40

输出样例#1

50
说明
时空限制:1000ms,128M
数据规模:

对于30%的数据: N10M25 N ≤ 10 , M ≤ 25

对于70%的数据: N200M1000 N ≤ 200 , M ≤ 1000

对于100%的数据: N10000M100000 N ≤ 10000 , M ≤ 100000

样例说明:

题目中存在3条路径:

4–>2–>3,该路线可通过20的流量

4–>3,可通过20的流量

4–>2–>1–>3,可通过10的流量(边4–>2之前已经耗费了20的流量)

故流量总计20+20+10=50。输出50。

网络流就是解决这一类题目,要理解网络流算法还是有一点难,我们一步一步讲。
首先比较容易想到的就是我找一条可行的边,然后把它每条边全部扣掉可行的流量,在答案里加上这个流量。可以说这样做是对的,而且这是网络流的核心思想就是这个。但是单单这样是不行的,因为这样会wa的

这里写图片描述

第一行就是我们刚刚的算法,可以发现这样的答案是20,但是正确答案是25。我们发现这样会漏掉一些答案。于是我们考虑在增广之后对这条边连一条反向边。但是这样做为什么是对的呢?其实这相当于一个撤销操作,我走逆流边,相当于把以前走过这条边的流量又推回去了。我们慢慢理解,再画一个图:

这里写图片描述

大概就是这样,而中间正反两边都走了就相当于没有走。

但是这样我们会发现一个问题:

这里写图片描述

假如我们每次都增广中间那一条,那我们要增广1e9次,这样就爆炸对不对?

那我们可能需要改进这种方法。我们每次跑一边bfs,把你的图分层,我每次只增广这个点下一层的点。这样就避免了一条路多次增广的情况。

那么我们贴一波代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
#include<queue>  
using namespace std;  

struct lxy{  
    int flow,next,to;  
}b[200005];  

int head[10005];  
int cnt=-1;  
int n,m,t;  
int layer[10005];//分层  
int cir[10005];  

void add(int op,int ed,int flow)  
{  
    cnt++;  
    b[cnt].next=head[op];  
    b[cnt].to=ed;  
    b[cnt].flow=flow;  
    head[op]=cnt;  
}  

bool bfs(int s)//分层,只走流量不为0的边  
{  
    memset(layer,0,sizeof(layer));  
    queue <int> d;  
    layer[s]=1;  
    d.push(s);  
    while(!d.empty())  
    {  
        int noww=d.front();  
        d.pop();  
        for(int i=head[noww];i!=-1;i=b[i].next)  
          if(layer[b[i].to]==0&&b[i].flow!=0)  
          {  
             layer[b[i].to]=layer[noww]+1;  
             d.push(b[i].to);  
          }  
    }  
    return layer[t];  
}  

int dfs(int u,int a)//增广,a代表还能增广多少流量  
{  
    if(u==t||a==0) return a;  
    int f=0;  
    int flow=0;//当前增广了多少流量  
    for(int i=cir[u];i!=-1;i=b[i].next)  
    {  
        if(layer[u]+1==layer[b[i].to]&&(f=dfs(b[i].to,min(a,b[i].flow)))>0)//f代表从这条路下去能增广多少  
        {  
            b[i].flow-=f;//边减去增广流量  
            b[i^1].flow+=f;//逆流边加上流量  
            flow+=f;//已经总增广了这么多  
            a=a-f;//还能增广的要减去  
            if(a==0)//不能增广就break  
                break;  
        }  
    }  
    return flow;  
}  

int dinic(int s)  
{  
    int ans=0;  
    while(bfs(s))//跑一边bfs,跑一边dfs,无法到达终点就退出  
    {  
        memset(cir,0,sizeof(cir));  
        ans=ans+dfs(s,0x7f7f7f7f);  
    }  
    return ans;  
}  

int main()  
{  
    int s;  
    scanf("%d%d%d%d",&n,&m,&s,&t);  
    for(int i=1;i<=n;i++)  
      head[i]=-1;  
    for(int i=1;i<=m;i++)//连边,虽然说走过才连逆流边,但是这里我们先把所有逆流边连好,流量为0。而且注意cnt从-1开始,这样每一条边的编号取反就是逆流边  
    {  
        int x,y,z;  
        scanf("%d%d%d",&x,&y,&z);  
        add(x,y,z);  
        add(y,x,0);  
    }  
    printf("%d",dinic(s));  
}  

这样我们就把这道题A了
最小割:最大流等于最小割

最小费用最大流:在最大流的基础上,我们对于每一条边流过单位流量定义一个价格,我们要求在保证最大流的情况下,花费的价格最小,求最大流和最小花费。

在学会最大流之后,这道题就变得非常简单了:我们用单位价格作为边权,逆流边边权为负,求最短路,每次增广最短路就行了。

日常贴代码:

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<queue>  
#include<cmath>  
using namespace std;  
struct lxy{  
    int to,next,flow,w;  
}b[100005];  

int n,m,s,t,cnt=-1,wn;  
int head[5005];  
bool vis[5005];  
int dis[5005];  

void add(int op,int ed,int flow,int w)  
{  
    cnt++;  
    b[cnt].next=head[op];  
    b[cnt].to=ed;  
    b[cnt].flow=flow;  
    b[cnt].w=w;  
    head[op]=cnt;  
}  

bool spfa(int s,int t)  
{  
    memset(dis,0x3f,sizeof(dis));  
    memset(vis,0,sizeof(vis));  
    queue <int> d;  
    d.push(s);  
    vis[s]=1;dis[s]=0;  
    while(!d.empty())  
    {  
        int now=d.front();  
        d.pop();vis[now]=0;  
        for(int i=head[now];i!=-1;i=b[i].next)  
          if(b[i].flow!=0&&dis[b[i].to]>dis[now]+b[i].w)  
          {  
            dis[b[i].to]=dis[now]+b[i].w;  
            if(vis[b[i].to]==0)  
              d.push(b[i].to),vis[b[i].to]=1;  
          }  
    }  
    if(dis[t]==0x3f3f3f3f) return false;  
    return true;  
}  

int dfs(int u,int a,int t)//唯一一个值得注意的是,我们要记录一个vis,一次增广不能重复走一个点,否则就死循环了(连逆流边就两个自然形成0权环)。  
{  
    vis[u]=1;  
    if(u==t||a==0) return a;  
    int k=0,f;  
    for(int i=head[u];i!=-1;i=b[i].next)  
      if(dis[u]+b[i].w==dis[b[i].to]&&b[i].flow!=0&&vis[b[i].to]==0)  
      {  
        f=dfs(b[i].to,min(a,b[i].flow),t);  
        b[i].flow-=f;  
        b[i^1].flow+=f;  
        k+=f;a-=f;  
        wn+=f*b[i].w;  
        if(a==0) break;  
      }  
    vis[u]=0;  
    return k;  
}  

int cost_flow(int s,int t)  
{  
    int ans=0;  
    while(spfa(s,t))  
      memset(vis,0,sizeof(vis)),ans+=dfs(s,0x3f3f3f3f,t);  
    return ans;  
}  

int main()  
{  
    scanf("%d%d%d%d",&n,&m,&s,&t);  
    for(int i=1;i<=n;i++) head[i]=-1;  
    for(int i=1;i<=m;i++)  
    {  
        int x,y,z,e;  
        scanf("%d%d%d%d",&x,&y,&z,&e);  
        add(x,y,z,e);  
        add(y,x,0,-e);  
    }  
    printf("%d ",cost_flow(s,t));  
    printf("%d",wn);  
}  

所以这两个板子有什么卵用么,感觉很板啊?
其实网络流题做多了,你就会体会到被网络流支配的恐惧。

网络流的应用一般用于二分三分四分以及n分图的匹配问题,比如说这道:P3386 【模板】二分图匹配

题目背景
二分图

题目描述
给定一个二分图,结点个数分别为 n,m n , m ,边数为 e e ,求二分图最大匹配数

输入输出格式
输入格式:
第一行,n,m,e

第二至 e+1 e + 1 行,每行两个正整数 u,v u , v ,表示 u,v u , v 有一条连边

输出格式:
共一行,二分图最大匹配

输入输出样例
输入样例#1: 复制
1 1 1
1 1
输出样例#1: 复制
1

说明
n,m10001un1vm n , m ≤ 1000 , 1 ≤ u ≤ n , 1 ≤ v ≤ m

因为数据有坑,可能会遇到 v>m v > m 的情况。请把 v>m v > m 的数据自觉过滤掉。

算法:二分图匹配

就是两边点的配对问题,这是最大流思想。
另外还有用最小割思想的,比如每个可以做两种选择,就可以连两种边,割一条边就等于做一种选择。然后根据题目条件连一连边,跑一下网络流,就可以得到答案。

因为网络流的脑洞都特别大,所以有时根本看不出来是网络流,而且就算看出是网络流,要把各种情况转化成连边也很难。所以网络流的难点不在跑,而在建图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值