『笔记』网络流

网络流

标签(空格分隔): 图论


网络流

实则为一种建模思想,可以将一些问题转换为图来解答

图的定义

一个图 \(G\) 是一个二元组,记为 \((V,E)\),或者记作 \(G(V,E)\),其中 \(V\) 为有限非空集合,成为 \(G\) 的顶点集,\(V\) 中的元素成为节点或者顶点,\(E\) 称为 \(G\) 的边的集合,\(\forall e_i \in E\),都有 \(V\) 中的结点与之对应,称 \(e_i\)\(G\) 的边。

\(E\) 是边集合,\(V\) 是点集合

网络流的基本概念

  • 容量:网络中的每条有向边 \(f(x,y)\) 都有一个给定的权值,称为边的容量,记为\(c(x,y)\) 。也可以\(e\in E\) , \(c(e)\) 容量,\(f(e)\) 流量

  • 源点、汇点:网络中的两个特殊节点。流量从源点产生,最后全部归于汇点。源点用 \(S\) 表示,汇点用 \(T\) 表示。

  • 流量:对于网络中的每条边 \((x,y)\)\(f(x,y)\)被称为该边的流量。流量需要满足以下三条性质:

    • 容量限制:对于每条边,流经该边的流量不得超过该边的容量,即\(f(x,y)\leq c(x,y)\)

    • 斜对称性:每条边的流量与其相反边的流量之和为 0,即 \(f(x,y)=-f(x,y)\)

    • 流量守恒:从源点流出的流量等于汇点流入的流量。

  • 满足这个规则的网络叫做流网络 \(G\)

    • \(V_x\in V-(s,t),\sum_{(v,x)\in E}^{}f(v,x)=\sum_{(x,v)\in E}^{}f(x,v)\)
  • 所有可能出现的流量的情况,称为可行流 \(f\) ,其中\(0\) 流也是可行流

  • 最大可行流为\(|f|_{max}\)

形象的图
此处输入图片的描述

抽象的图
此处输入图片的描述 最大流为 \(10\)

  • 两个可行流方案相加,是一个新的可行流方案
    \(|f_1+f_2|=|f_1|+|f_2|\)

  • 在原图 \(G\) 上增添反向边,根据原图的可行流 \(f\) ,对应一个新图:残余网络 \(G_f\)。记录每条边的剩余容量

    • 反向边只存在于参与网络,在原图上并不存在

    • 可行流各不相同,参与网络也各不相同

  • 在参与网络上寻找 \(s\to t\) 的路径为增广路

    • 残余网络中不存在增广路的充要条件是当前流已是最大流

常见问题

最大流,最小割,费用流

最大流算法

贪心算法:

1、从 \(s \to t\)寻找一条路径,路径上所有的边满
足容量限制 \(f(e)\leq c(e)\)

2、如果不存在满足条件的路径,则结束算法。否
则沿着这条路径尽可能的增加流量,返回第 \(1\) 步。

Ford-Fulkerson(FF)算法

基于增广路的最大流方法

时间复杂度:每次 \(DFS\)\(O(m)\),设最大流的流量为 \(f\) ,由于每次流量至少增加 \(1\) ,总时间复杂度为\(O(fm)\)

显然慢成狗/ww

Edmond-Karp(EK)算法

最短路增广算法(\(BFS\)算法)

#include<iostream>
#include<cstring>
#include<queue>
#define int long long 
using namespace std;
const int N=1e2+9;
const int M=5e3+9;
struct node{
    int c;
    int last;
    int to;
}e[N];
int n,m,s,t,cnt;
int maxflow;
int head[N];
int c[M];

void add(int from,int to,int dis)
{
    e[++cnt].last=head[from];
    e[cnt].to=to;
    e[cnt].c=dis;
    head[from]=cnt;
    e[++cnt].last=head[to];
    e[cnt].to=from;
    e[cnt].c=0;
    head[to]=cnt;
}

int vis[N],pre[N];//访问,前驱 
int incf[N];//可增加流量数组
const int inf=1<<29;

bool bfs()
{
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(s);
    vis[s]=1;incf[s]=inf;//发出的从无限大凯始
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=e[i].last)
        {
            if(e[i].c)//有容量 
            {
                int v=e[i].to;
                if(vis[v]) continue;
                incf[v]=min(incf[u],e[i].c);
                pre[v]=i;//记录到达点的边的编号
                q.push(v);
                vis[v]=1;
                if(v==t) return 1;//如果到了终点 
            }
        }
    } 
    return 0; 
} 

void update()
{
    int u=t;//从后往前找
    while(u!=s)
    {
        int i=pre[u];
        e[i].c-=incf[t];//反方向边编号为奇数,正方向为偶数 
        e[i^1].c+=incf[t];
        u=e[i^1].to;
    } 
    maxflow+=incf[t];
}
signed main()
{
    cin>>n>>m>>s>>t;
    cnt=1;
    maxflow=0;
    while(m--)
    {
        int u,v,c;
        cin>>u>>v>>c;
        add(u,v,c);
    }
    while(bfs()) update();
    cout<<maxflow<<endl;
}

Dinic算法

基于分层图的多路增广算法

算法思想

  1. 在残余网络上,用BFS从源点 \(s\) 到汇点 \(t\) 构造
    分层图;
  2. 在当前分层图上,使用DFS进行多路增广,
    在回溯时实时更新剩余容量。

直至在残余网络中,无法从源点 \(s\) 到达汇点 \(t\)

当前弧优化:记录一下当前没有满流的边

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#define int long long 
using namespace std;
const int N=1e4+9;
const int M=1e5+9;
const int inf=2e9+9;
struct node{
    int last;
    int to;
    int c;
}e[M];
int cur[N],head[N],d[N];
int n,m,s,t,cnt;
void add(int from,int to,int dis)
{
    e[++cnt].last=head[from];
    e[cnt].to=to;
    e[cnt].c=dis;
    head[from]=cnt;   
}
bool bfs()
{
    memset(d,-1,sizeof(d));
    queue<int> q;
    q.push(s);
    d[s]=0;
    cur[s]=head[s];
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=e[i].last)
        {
            int v=e[i].to;
            int w=e[i].c;
            if(d[v]==-1&&w)
            {
                d[v]=d[u]+1;
                cur[v]=head[v];
                q.push(v);
                if(v==t) return true;
            }
        }
    }
    return false;
}
int dfs(int u,int lim)
{
    int flow=0;
    if(u==t) return lim;
    for(int i=cur[u];i&&flow<lim;i=e[i].last)
    {
        cur[u]=i;//当前弧优化 
        int v=e[i].to;
        int w=e[i].c;
        if(w&&d[v]==d[u]+1)
        {
            int f=dfs(v,min(w,lim-flow));
            if(!f) d[v]=-1;
            e[i].c-=f;
            e[i^1].c+=f;
            flow+=f;
        }
    }
    return flow;
}
int dinic()
{
    int maxflow=0;
    int flow=0;
    while(bfs())
        while(flow=dfs(s,inf))
            maxflow+=flow;
    return maxflow;
}
signed main() 
{
    scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
    cnt=1;
    while(m--)
    {
        int u,v,c;
        scanf("%lld%lld%lld",&u,&v,&c);
        add(u,v,c);
        add(v,u,0);
    }
    printf("%lld\n",dinic());
    return 0;
}

最大流-最小割定理

1、\(f\)\(G\) 的一个最大流
2、残余网络 \(G_f\) 中不存在增广路
3、对于 \(G\) 的某个割 \([S,T]\),有 \(|f|=c[S,T]\)

\(f\) 对应的残余网络 \(G_f\) 中,构造点集 \(S\):从源点 \(s\) 可以到达的顶点的顶点 \(v\) 组成的集合

$\because $ 残余网络已经无法找到增广路,意味着无法到达汇点 \(t\)

\(\therefore\) \([S,V-S]\)\(s-t\)

\(\because\) 割中都是满流边

\(\therefore\) $$|f|=\sum_{u\in S}\sum_{v\in T }f(u,v)-\sum_{u\in T}\sum_{v\in S}f(u,v)=\sum_{u\in S}\sum_{v\in T }c(u,v)=c[S,T]$$

\(\therefore f\) 是最大流

切割
  • \(G=(V,E)\)的割:\([S,V-S]\)

  • 将点集 \(V\) 划分为 \(S\)\(V-S\) 两个部分

  • \([S,V-S]\) 代表一个边集合

  • 割的容量:边集中所有边的容量之和 \(c(S,V-S)\)

  • 割的净流量(net flow):边集中所有边的流量之和定义为 \(f(S,V-S)\)
    此处输入图片的描述
    可以发现,最大流=最小割

如果 \(s\in S,t\in (V-S)\),则成为\(s-t\)\([S,T]\)

对于一个网络,为了保证没有从 \(s\)\(t\) 的路径,要删去的边:

  • \(s-t\) 割的容量:\(c(S,T)\)

  • \(s-t\) 割的净流量:\(f(S,T)\)

一个流网络 \(G\) 中,设任意一个流 \(f\) ,且 \([S,T]\)\(G\) 的一个割,则通过割的净流量为 \(f(S,T)=|f|\)

通过割的净流就是网络的可行流

流网络 \(G\) 中,设任意一个流为 \(f\) ,任意一个割为\([S,T]\)

\(\because\) \(S\) 中除了源点以外的其他点都流量守恒。

\(\therefore\) \(|f|\)\(S\) 的出边的总流量减去 \(S\) 的入边的总流量

\(\because\) \(S\) 出边的总容量为上限,一定大于等于 \(S\) 的出边的总流量减去 \(S\) 的入边的总流量

\(\therefore\) \(|f|\leq c[S,T]\)

\[|f|=\sum_{v\in V} f[s,v] \]

\[f[S,T]=f(S,V)-f(S,S)=f(S,V)=f(s,V)+f(S-(s),V)=f(s,V)=|f| \]

\[V_x \in V-(s,t),\ \sum_{(v,x)\in E}f(v,x)=\sum_{(x,v)\in E} f(x,v) \]

\[|f|=\sum_{u\in S} \sum_{v\in T}f(u,v)-\sum_{u\in T}\sum_{v\in S}f(u,v) \]

\[|f|=f(S,T)=\sum_{u\in S} \sum_{v\in T}f(u,v)\leq \sum_{u\in S}\sum_{v\in T}c(u,v) \]

网络的最大流必定不超过最小割的容量

最大流算法的应用

二分图的匹配问题

P2756 飞行员配对问题

上下界网络流

待补全

费用流算法

基于SPFA的实现

当流网络的边上增加了费用之后,可以在残余网络上沿着最短路增广,反向边的费用就是正向边的值取反,由于流网络中出现负权边,需要用SPFA算法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<algorithm>
#include<map>
#include<cmath>
#define int long long 
using namespace std;
const int N=1e4+1;
const int M=2e6+9;
const int inf=2e10+9;
const int INF = 0x3f3f3f3f;
struct node{
    int last;
    int c;
    int to;
    int cost;
}e[M];
int ret=0ll;
int head[N],cnt;
int cur[N],n,m,s,t;
int vis[N],dis[N];
int d[N];
void add(int from,int to,int dis,int cost)
{
    e[++cnt].last=head[from];
    e[cnt].to=to;
    e[cnt].c=dis;
    e[cnt].cost=cost;
    head[from]=cnt;
}
bool spfa() 
{
    bool bl = 0;
    memset(dis, 127, sizeof(dis));
    memcpy(cur, head, sizeof(head));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    vis[s] = 1;
    while (!q.empty()) 
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for (int i = head[u]; i; i = e[i].last) 
        {
            int v = e[i].to;
            if (e[i].c && dis[v] > dis[u] + e[i].cost) 
            {
                dis[v] = dis[u] + e[i].cost;
                if (!vis[v]) q.push(v), vis[v] = 1;
                if(v == t) bl = 1;
            }
        }
  }
  return bl;
}
int dfs(int u,int lim)
{
    int flow=0;
    if(u==t) return lim;
    vis[u]=1;
    for(int &i=cur[u];i&&flow<lim;i=e[i].last)
    {
        int v=e[i].to;
        int w=e[i].c;
        if(!vis[v]&&w&&dis[v]==dis[u]+e[i].cost)
        {
            int f=dfs(v,min(w,lim-flow));
            if(!f) dis[v] = 0x7fffffff;
            ret+=f*e[i].cost;
            e[i].c-=f;
            e[i^1].c+=f;
            flow+=f; 
            if(flow == lim) break;
        }
    }
    vis[u]=0;
    return flow;
}
int dinic()
{
    int maxflow=0;
    int flow=0;
    while(spfa())
        while(flow=dfs(s,inf))
            maxflow+=flow;
    return maxflow;
}
signed main()
{
    scanf("%lld%lld%lld%lld",&n,&m,&s,&t);
    cnt=1;
    while(m--)
    {
        int x,y,z,cost;
        cin>>x>>y>>z>>cost;
        add(x,y,z,cost);
        add(y,x,0,-cost);
    }
    int ans=dinic();
    cout<<ans<<" "<<ret<<endl;
    return 0;
} 

典型例题讲解

P4015 运输问题

P4016 负载平衡问题

P2770 航空路线问题

P4014 分配问题

P4013 数字梯形问题

P3358 最长k可重区间集问题

P3357 最长k可重线段集问题

P4012 深海机器人问题

P3356 火星探险问题

P1251 餐巾计划问题

最小割模型的应用

典型例题

UVA1660 有线电视网络

P2762 太空飞行计划问题

最大权闭合图

  • 闭合图: 是有向图\(G=(V,E)\) 的一个点集,且该点集的所有出边都还指向该点集,即闭合图的任意一点的任意后继也一定在闭合图中

  • 最大权闭合图:(如给 \(G\) 中每个点 \(v\) 分配点权 \(w_v\)) 点权之和最大的闭合图,即最大化 \(\sum_{v\in V}w_v\)

此处输入图片的描述

左图中闭合图共有 \(9\) 个:

\(∅\), {\(5\)} , {\(2,5\)} , {\(4,5\)} , {\(2,4,5\)} , {\(3,4,5\)} ,
{\(1,2,4,5\)} , {\(2,3,4,5\)} ,{\(1,2,3,4,5\)}

最大权闭合图为 {\(3,4,5\)},权和为 \(4\)

  • 闭合图的性质反映了事件间的必要条件的关系:一个事件的发生,它所需要的所有前提也都要发生。最大权闭合图对应了获益最大或效率最高的事件选择集合。

二分图的最小点权覆盖集与最大点权独立集

待添加

posted @ 2021-02-02 13:10  琼瑾  阅读(172)  评论(3编辑  收藏  举报
Live2D