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

网络最大流学习笔记

时间:2023-02-09 22:13:12浏览次数:55  
标签:笔记 int 复杂度 网络 long 学习 dep 流量 maxflow

网络最大流学习笔记

形式化的定义

网络流问题是指:
给定一张有向图(称之为网络) \(G(V, E)\), 每条边都有一个权值(称之为容量) \(c(u,v)\), 对于 \((u,v)\notin E\) 有 \(c(u,v)=0\)
定义两个特殊的点:源点 \(s\in V\) 和 汇点 \(t\in V\) \((s\ne t)\)

设 \(f(u,v)\) 定义在二元组 \((u\in V,v\in V)\) 上的实数函数且满足

\[f(u,v)=\left\{ \begin{aligned} &f(u,v),&(u,v)\in E\\ &-f(v,u),&(v,u)\in E\\ &0,&(u,v)\notin E,(v,u)\notin E \end{aligned}\right. \]

容易发现其满足如下的性质:

  1. 容量限制:对于每条边,流经该边的流量不得超过该边的容量,即,\(f(u,v)\leq c(u,v)\)
  2. 斜对称性:每条边的流量与其相反边的流量之和为 0,即 \(f(u,v)=-f(v,u)\)
  3. 流守恒性:从源点流出的流量等于汇点流入的流量,即 \(\forall x\in V-\{s,t\},\sum_{(u,x)\in E}f(u,x)=\sum_{(x,v)\in E}f(x,v)\)

那么 \(f\) 称为网络 \(G\) 的流函数。对于 \((u,v)\in E\),\(f(u,v)\) 称为边的 流量,\(c(u,v)-f(u,v)\) 称为边的 剩余容量。整个网络的流量为 \(\sum_{(s,v)\in E}f(s,v)\),即 从源点发出的所有流量之和

通常而言, 要求的流量指的是 整个网络的流量

通俗理解

通俗的讲, 可以把流理解为水流, 边理解为水管, 源点为水池, 汇点为水龙头, 水管有粗有戏, 问最多能流出多少水。

网络最大流的求解

网络最大流的常见求解方法都是基于 反悔贪心, 具体地说, 贪心地进行流, 然后利用一条 反向边 用来 抵消 先前流过的流量, 相当于是抵消了先前错误的决定

Ford-Fulkerson

通常来说, 在求解网络流的过程中, 每次在图上找到一条路径, 满足:路径上所有边剩余流量都 \(> 0\)(称之为增广路) ,然后找到这条路径上边权最小的边(限制这一条路径能流的流量的边), 把这条路径上所有边的实际流量都加上这一条边的流量, 再把这条边上所有反边的实际流量都扣除掉这一条边的流量(等价于 \(c(u,v)\) 的增加, 可以用来反悔)

OI-wiki上的一个例子

第一次走时, 给 \((u,v)\) 增加了流量, 给 \((v,u)\) 扣除了流量(剩余流量增加), 那么从 \(p\) 再流时可以 \(v\) 回流一部分(或者如图中,全部回流)到 \(u\), 抵消掉 \(u\) 不需要贡献给 \(v\) 的流量, 再从 \(u\) 流去其他节点, 这等价于从 \(u\) 直接流出到其他节点

如果直接暴力 DFS 找最大流, 时间复杂度为 \(O(|E||f|)\) 其中 \(|f|\) 为图中的最大流, 因为每轮增广流量单调递增, 所以最多有 \(|f|\) 次增广, 单轮增广最坏 \(O(|E|)\), 这个复杂度很坏, 但是可以在这个求最大流的思想上对其继续优化。

Dinic

网络流卡 Dinic 的出题人都有才无德

Dinic 利用 BFS 分层和 DFS 找增广路保证复杂度, 具体地说, 在每次 DFS 找增广路之前先利用 BFS 给图分层, 保证 DFS 不会走回头路, 随后把找出来的新流并入原来的流中, 持续找增广路直到无路可走为止
注意到如果一个点的度数很大, 那可能需要枚举每一条边来判断是否需要流出流量, 对此我们可以引入当前弧优化——所有流满的边都不需要流, 那么维护第一条还需要流的边即可
示例代码如下

#include <bits/stdc++.h>

using namespace std;
const int N = 250, M = 1e4 + 1000;
const long long INF = 0x3f3f3f3f;

int n, m, s, t;
int idx = 0, h[N], e[M], ne[M];
//w:存的是这条边的最大流量
long long w[M];

inline void add(int a, int b, long long c){
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

//now:当前弧 dep:点的深度(分层图)
int now[N], dep[N];

bool bfs(){
    //每次都初始化深度
    memset(dep, 0x3f, sizeof dep);

    dep[s] = 0;
    now[s] = h[s];

    queue<int> Q;
    Q.push(s);

    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();

        for (int i = h[u]; ~i; i = ne[i]) {
            int v = e[i];
            //当前弧优化
            if (w[i] > 0 && dep[v] == INF) {
                Q.push(v);
                now[v] = h[v];
                dep[v] = dep[u] + 1;
                //在所有流量非空的边连起来的图里还能找到终点, 也就是还能再流
                if (v == t)
                    return true;
            }
        }
    }
    return false;
}

int dfs(int u, long long maxflow){
    //流到头了或者流不出了就返回
    if (u == t || !maxflow)
        return maxflow;

    //flow 是当前这个点的流量
    long long k = 0, flow = 0;
    for (int i = now[u]; ~i && maxflow > 0; i = ne[i]) {
        now[u] = i;
        int v = e[i];

        if (w[i] > 0 && (dep[v] == dep[u] + 1)) {
            //当前点能流出的最多流量与这条边能承受的流量取min
            k = dfs(v, min(maxflow, (long long)w[i]));
            if (k == 0)
                dep[v] = INF;
            //在这条边上增加流量, 相当于在c(u,v)上扣除流量
            w[i] -= k;
            //i ^ 1是反边, 同上
            w[i ^ 1] += k;
            flow += k;
            //maxflow是这个点还能流出的最大流量
            maxflow -= k;
            if(!maxflow)
                break;
        }
    }
    return flow;
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d%d%d%d", &n, &m, &s, &t);

    for (int i = 1; i <= m; i++) {
        int a, b;
        long long c;
        scanf("%d%d%lld", &a, &b, &c);
        //一个技巧:正边编号偶数, 反边编号奇数, 那么任何一条边编号 xor 1 都是自己的反边
        //反边的初始流量为 0
        add(a, b, c), add(b, a, 0);
    }

    long long ans = 0;
    while (bfs()) {
        ans += dfs(s, INF);
    }
    printf("%lld\n", ans);

    return 0;
}

时间复杂度分析
Dinic 在一般图上的复杂度最坏为 \(O(|V|^2|E|)\) 然而极其难卡
在二分图上的复杂度是 \(O(\sqrt{|V|} |E|)\), 如果所有边流量都为 \(1\), 复杂度为 \(O(|E|\min(|E|^{\frac{1}{2}}, |V|\sqrt|V|))\)
参见 OI-wiki上的Dinic复杂度分析

例题

最大流板子
网络流拆点

标签:笔记,int,复杂度,网络,long,学习,dep,流量,maxflow
From: https://www.cnblogs.com/lostintianyi/p/17107333.html

相关文章

  • LeNet5网络
    Pytorch搭建LeNet-5网络PyTorch实战:经典模型LeNet5实现手写体识别https://github.com/chuanqi305/LeNet5https://github.com/IQ250/LeNet-by-Numpy......
  • 自我介绍和学习记录
    这个作业属于哪个课程https://edu.cnblogs.com/campus/fzzcxy/2023learning这个作业要求在哪里https://edu.cnblogs.com/campus/fzzcxy/2023learning/homework/1......
  • kubernetes(k8s)基础学习-kubernetes是什么?有什么用?
    kubernetes(k8s)基础学习-kubernetes是什么?一、认识DockerDocker是什么先来看看Docker的图标:一条鲸鱼背上驮着四方形块的物品,就像一条海运船上装满集装箱,集装箱里......
  • C语言学习1
    常见的校招/社招要求:1.计算机语言(C/C++/JAVA等);2.数据结构和算法3.操作系统;4.计算机网络+网络编程;5.数据库;6.脚本语言。计算机语言的学习不要贪多而不精,选一门深入学......
  • 网络协议-ssh基础
    ssh连接连接准备客户端如果想要连接服务端并登录,首先需要在本地生成一对密钥(私钥和公钥)。其中私钥文件:~/.ssh/id_rsa公钥文件:~/.ssh/id_rsa.pub然后将公钥......
  • 产研指南针的量化指标实践笔记
    背景在公司和业务发展到一定阶段,高层管理者会逐步期望从直觉化的管理逐步转向量化的关键指标管理;同时从hr层面okr和kpi的考核逐步从直觉化的定性考核,转变为数据化指标考核......
  • 学习 C++第三天
    转义字符\?在书写连续多个问号时使用,防止他们被解析成三字母词\'用于表示字符常量\“用于表示一个字符串内部的双引号\\用于表示一个反斜杠,防止它被解释为一个转义序列......
  • Linux学习-DAY8
    第5章用户身份与文件权限5.1用户身份与能力UID=0:      root用户UID=1~999:  系统用户UID=>1000:   普通用户注意:如果创建用户的时候手动指定了用户U......
  • 自我介绍和学习心得
    自我介绍和学习心得一.自我介绍1.我的基本信息,性格,爱好等我的基本信息我叫李志恒,男,来自河南南阳,之前在材料成型及控制工程1班,现在在计算机科学与技术1班性格性格比较......
  • 第四天笔记 函数
    第四天笔记(函数)函数概述函数相当于一个代码空间,他里面可以存储一些代码片段,就是利用函数来减少冗余代码的出现,形成对应的复用。一般我们会将一些功能性代码抽取放入到......