首页 > 其他分享 >【博客】网络流&&费用流

【博客】网络流&&费用流

时间:2024-02-15 20:13:14浏览次数:26  
标签:费用 head 增广 int cap flow 博客 客户 &&

网络流

前言

当听到网络 流量之后 感觉是在充话费

网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。

它模拟了水流从起点经过复杂的网络流向终点的过程

就像自来水厂的水 经过无数根水管子 流到了家里

最大流就是最多有多少水流到了家里


算法流程

EK算法

首先,引入增广路的概念。

一条从s到t的路径,水流流过这条路,使得当前可以到达t的流量可以增加。

知道了增广路的概念,就可以很显然的想出一种贪心做法,不断通过bfs寻找增广路并处理和累加答案,直到找不到增广路,答案就是最大流。

但是它是错的

如图

image

如果我们找到了s-1-2-3-t 这条路径 流量为1

那么图会变成这样

image

再找不到了增广路了 但是显而易见原图的最大流是2

所以 我们引入了后悔药反向边

在流量减去最小值的时候,将反向边加上最小值

就像这样

image

于是多了一条增广路 s-3-2-1-t

综上所述 我们得到了一种求最大流的方法

总结一下就是

通过没流满的边或新建的反向边找增广路增广

在找不到增广路时算法结束

其实这个思想就是EK算法的思想

代码大概是这样的

#define maxn 250
#define INF 0x3f3f3f3f

struct Edge {
  int from, to, cap, flow;

  Edge(int u, int v, int c, int f) : from(u), to(v), cap(c), flow(f) {}
};

struct EK {
  int n, m;             // n:点数,m:边数
  vector<Edge> edges;   // edges:所有边的集合
  vector<int> G[maxn];  // G:点 x -> x 的所有边在 edges 中的下标
  int a[maxn], p[maxn];  // a:点 x -> BFS 过程中最近接近点 x 的边给它的最大流
                         // p:点 x -> BFS 过程中最近接近点 x 的边

  void init(int n) {
    for (int i = 0; i < n; i++) G[i].clear();
    edges.clear();
  }

  void AddEdge(int from, int to, int cap) {
    edges.push_back(Edge(from, to, cap, 0));
    edges.push_back(Edge(to, from, 0, 0));
    m = edges.size();
    G[from].push_back(m - 2);
    G[to].push_back(m - 1);
  }

  int Maxflow(int s, int t) {
    int flow = 0;
    for (;;) {
      memset(a, 0, sizeof(a));
      queue<int> Q;
      Q.push(s);
      a[s] = INF;
      while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        for (int i = 0; i < G[x].size(); i++) {  // 遍历以 x 作为起点的边
          Edge& e = edges[G[x][i]];
          if (!a[e.to] && e.cap > e.flow) {
            p[e.to] = G[x][i];  // G[x][i] 是最近接近点 e.to 的边
            a[e.to] =
                min(a[x], e.cap - e.flow);  // 最近接近点 e.to 的边赋给它的流
            Q.push(e.to);
          }
        }
        if (a[t]) break;  // 如果汇点接受到了流,就退出 BFS
      }
      if (!a[t])
        break;  // 如果汇点没有接受到流,说明源点和汇点不在同一个连通分量上
      for (int u = t; u != s;
           u = edges[p[u]].from) {  // 通过 u 追寻 BFS 过程中 s -> t 的路径
        edges[p[u]].flow += a[t];      // 增加路径上边的 flow 值
        edges[p[u] ^ 1].flow -= a[t];  // 减小反向路径的 flow 值
      }
      flow += a[t];
    }
    return flow;
  }
};

概念&&性质

我们需要补充几个概念

不学好像也行?

  1. 在网络流中,有一个源点s,还有一个汇点t,边的权值称为容量,且边为有向边
  2. 边的当前的流量应该小于等于容量(不然水管不就爆了)
  3. 边的当前的流量等于反向边的流量的相反数
  4. 每个中间点流入的等于流出的
  5. 整个图的流量为从源点出发的流量之和(流入汇点的之和)
  6. 残量网络:没流满的边和反向边所形成的图(找增广路其实就是在它上面找的)

所以EK算法就是在残量网络上求增广路的算法

还有对它正确性的证明 利用了最大流等于最小割


Dinic算法

Dinic算法是EK算法的优化

它有三处优化

  1. 分层图优化:在增广之前跑BFS建出分层的残量网络,可以同时多路增广
  2. 当前弧优化:将链式前向星的第一条边传址调用 直接修改它的值
  3. -1优化:在同一次DFS中 一个点无法引出任意增广路 直接删掉

好像多路增广不算优化 OI Wiki上这么说的

可能是由于大量网络资料的错误表述引发以讹传讹的情形,相当数量的选手喜欢将当前弧优化和多路增广并列称为 Dinic 算法的两种优化。实际上,当前弧优化是用于保证 Dinic 时间复杂度正确性的一部分,而多路增广只是一个不影响复杂度的常数优化。

利用这三个优化 EK算法就成了Dinic算法

struct MF
{
    struct edge
    {
        int v, nxt, cap, flow;
    } e[N];

    int head[N], cnt = 0;

    int n, S, T;
    ll maxflow = 0;
    int dep[N], cur[N];

    void init()
    {
        memset(head, -1, sizeof head);
        cnt = 0;
    }

    void addedge(int u, int v, int w)
    {
        e[cnt] = {v, head[u], w, 0};
        head[u] = cnt++;
        e[cnt] = {u, head[v], 0, 0};
        head[v] = cnt++;
    }

    bool bfs()//建分层图
    {
        queue<int> q;
        memset(dep, 0, sizeof(int) * (n + 1));

        dep[S] = 1;
        q.push(S);
        while (q.size())
        {
            int u = q.front();
            q.pop();
            for (int i = head[u]; ~i; i = e[i].nxt)
            {
                int v = e[i].v;
                if ((!dep[v]) && (e[i].cap > e[i].flow))
                {
                    dep[v] = dep[u] + 1;
                    q.push(v);
                }
            }
        }
        return dep[T];
    }

    int dfs(int u, int flow)
    {
        if ((u == T) || (!flow)) return flow;

        int ret = 0;
        for (int& i = cur[u]; ~i; i = e[i].nxt)//当前弧优化
        {
            int v = e[i].v, d;
            if ((dep[v] == dep[u] + 1) &&(d = dfs(v, min(flow - ret, e[i].cap - e[i].flow))))
            {
                ret += d;
                e[i].flow += d;
                e[i ^ 1].flow -= d;
                if (ret == flow) return ret;
            }
        }
        if(ret!=flow) dep[u]=-1;//-1优化
        return ret;
    }

    void dinic()
    {
        while (bfs())
        {
            memcpy(cur, head, sizeof(int) * (n + 1));
            maxflow += dfs(S, INF);
        }
    }
} mf;

例题

养猪

尼克在一家养猪场工作,这家养猪场共有M间锁起来的猪舍,由于猪舍的钥匙都给了客户,所以尼克没有办法打开这些猪舍,客户们从早上开始一个接一个来购买生猪,他们到达后首先用手中的钥匙打开他所能打开的全部猪舍,然后从中选取他要买的生猪,尼克可以在此期间将打开的猪舍中的猪调整到其它开着的猪舍中,每个猪舍能存放的猪的数量是没有任何限制的。买完猪后客户会将他打开的猪舍关上。

好在尼克事先知道每位客户手中有哪些钥匙,要买多少猪,以及客户到来的先后次序。请你写一个程序,帮助尼克求出最多能卖出多少头生猪。


这题的主要问题是每次被打开的房间内的猪可以相互移动。为了解决这个问题,不妨换个角度来思考:实际上如果某客户取的猪是以前从其他的房间过来的,那么是不是可以就让这个客户到它原先所在的房间去取呢?即猪本身并不移动,而是钥匙重新分配,即客户拥有之前来的客户的某些钥匙。

例如客户1有3和4的钥匙,客户2有4和5的钥匙,那么客户2就等价拥有3、4和5这3把钥匙。又如果客户3有5和6的钥匙,他又等价拥有3、4、5和6的钥匙。

也就是说:只要某客户拥有的钥匙和之前的某些客户(他们的钥匙也有可能是别人给的)有交集,那么那些客户就相当于把他们的钥匙都给了这个客户。

这样按照顺序预处理后,客户前后到来的有序限制和猪可以移动的不利因素就不存在了,构图后求最大流即可。构图方式是:图中的点有客户和房间,增加源点和汇点,源点到房间连边,容量为房内猪的数量;客户到汇点连边,容量为客户的需求;房间到客户,只要客户有该房间的钥匙就连边,容量为无穷大。

P3163 [CQOI2014] 危桥


网络流模型

当然 很多问题都能用网络流解决

关键在于如何建图

通常通过最小割转化为最大流

二选一模型

建模方式:

源点向每个物品连边,边权为收益,割掉该边表示不选该物品;

每个物品向汇点连边,边权为代价,割掉该边表示选该物品;

物品之间连边,边权和意义视约束而定。

最终答案=总收益-最小割。

例如P1646 [国家集训队] happiness

最大权闭合子图模型

给定一张有向图,求一个权值和最大的点集S,使得S中任意点可达的点都在S内。

建模方式:

源点向点权为正的点连边,边权为点权,割掉该边表示不选该点;

点权为负的点向汇点连边,边权为点权的绝对值,割掉该边表示选该点;

将原图中的边照搬到新图上,边权为inf。

最终答案=正点权和-最小割。


费用流

在最大流中 我们贪心地在残量网络上寻找增广路增广

现在多了费用的情况下

我们应该在残量网络上沿着费用最小的路径增广

此时残量网络上反向边的费用应该是原边费用的相反数

也就是说会出现负边权

所以我们应该使用SPFA寻找最短路

所以算法的核心是最短路与最大流操作交替进行

在求最短路的时候顺便把图分层 可以进行多路增广

同样的 可以用当前弧优化-1优化

#include <bits/stdc++.h>

using namespace std;
const int N = 5e3 + 5, M = 1e5 + 5;
const int INF = 0x3f3f3f3f;
int n, m, tot = 1, head[N], cur[N], ter[M], nxt[M], cap[M], cost[M], dis[N], ret;
bool vis[N];

void add(int u, int v, int w, int c)
{
    ter[++tot] = v;
    nxt[tot] = head[u];
    head[u] = tot;
    cap[tot] = w;
    cost[tot] = c;
}

void addedge(int u, int v, int w, int c)
{
    add(u, v, w, c);
    add(v, u, 0, -c);
}

bool spfa(int s, int t)
{
    memset(dis, 0x3f, sizeof(dis));
    memcpy(cur, head, sizeof(head));
    std::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 = nxt[i])
        {
            int v = ter[i];
            if (cap[i] && dis[v] > dis[u] + cost[i])
            {
                dis[v] = dis[u] + cost[i];
                if (!vis[v]) q.push(v), vis[v] = 1;
            }
        }
    }
    return dis[t] != INF;
}

int dfs(int u, int t, int flow)
{
    if (u == t) return flow;
    vis[u] = 1;
    int ans = 0;
    for (int &i = cur[u]; i && ans < flow; i = nxt[i])
    {
        int v = ter[i];
        if (!vis[v] && cap[i] && dis[v] == dis[u] + cost[i])
        {
            int x = dfs(v, t, std::min(cap[i], flow - ans));
            if (x) ret += x * cost[i], cap[i] -= x, cap[i ^ 1] += x, ans += x;
        }
    }
    vis[u] = 0;
    return ans;
}

int mcmf(int s, int t)
{
    int ans = 0;
    while (spfa(s, t))
    {
        int x;
        while ((x = dfs(s, t, INF))) ans += x;
    }
    return ans;
}

int main()
{
    int s, t;
    scanf("%d%d%d%d", &n, &m, &s, &t);
    while (m--)
    {
        int u, v, w, c;
        scanf("%d%d%d%d", &u, &v, &w, &c);
        addedge(u, v, w, c);
    }
    int ans = mcmf(s, t);
    printf("%d %d\n", ans, ret);
    return 0;
}

例题

P2223 [HNOI2001] 软件开发

芯片难题 Chips Challenge

P4068 [SDOI2016] 数字配对

这回真写完了 撒花撒花


引用来源

网络流 - 知乎 (zhihu.com)

最大流 - OI Wiki (oi-wiki.org)

【知识点】网络流常见模型

费用流 - OI Wiki (oi-wiki.org)

信息学中“序”的浅析 - 桃子在路上 - 博客园 (cnblogs.com)

标签:费用,head,增广,int,cap,flow,博客,客户,&&
From: https://www.cnblogs.com/zysssss/p/18016536

相关文章

  • 欢迎来到 HZJ 的博客园
    前期提要原先我的博客是建立在洛谷上的原洛谷博客,但最近洛谷走向国际化,要把洛谷改成文章区,要经过一定时间的系统维护。我为了避免长时间的洛谷博客维护,就迁移到了博客园。我最近研究了一下博客园,其实洛谷博客要比博客园稍逊一筹的。博客园有\(LaTeX\)、有代码格式,有网站引......
  • 反悔贪心(模拟费用流)
    反悔贪心(模拟费用流)贪心本身是不能反悔的,但如果当前最优解不是全局最优解,我们就需要通过反悔来贴近全局最优解。一般用堆来实现,即堆中维护当前可以用来反悔的决策,然后每次取最优的决策。其实从更一般的角度,反悔贪心就是建出费用流模型,然后用数据结构来模拟增广的操作。一些例题......
  • 关于“博客园”经济脱困的一些看法
    作为和CSDN同为中国IT领域最大的技术博客网站,一个赚的盆满钵满,一个到了穷的要关门大吉了,这个发展也是耐人寻味的。不得不说,之所以自己选择在博客园而不是CSDN,其主要原因就是没有那些讨厌的广告,使用起来十分简洁和方便。但是这个博客园发展到如今这个难以维继的局面也是没有预料到......
  • 博客园配置
    Awescnb1.安利几款好看的博客园主题-CryFace-博客园(cnblogs.com)2.Awescnb手册|Awescnb(gitee.io)3.Awescnb配置选项(yuque.com)代码高亮显示行号Mac风格代码字体:FiraCode主题:atom-one-dark博客侧边栏公告无页面定制CSS代码禁用模板默认CSS#......
  • 给博客园捐些款:发个阿里云广告,对园子脱困很重要:阿里云上部署幻兽帕鲁
    相关:https://www.cnblogs.com/cmt/p/17995766由于博客园官方比较poor,而我也比较poor,但是考虑到不要让博客园关门了,毕竟这么多年了,而且自己在这上面的东西也蛮多的,搬家也挺费劲的,最为关键的是自己不喜欢别家的那种全是广告和充费的那种,动不动就要付费的那种,最终还是决定在自己......
  • 记录一下自定义博客园主题过程
    前言以前使用的都是默认的博客园主题,最近刚好有空,着手定制以下自己的博客园主题。最终效果参考当前的博客,如果看不到则需要在博客园首页头像处悬停关闭简洁模式思路是尽量保持原有结构,不进行破坏性改动,以css样式为主(当前只添加了两个js方法用于主题切换和判断是否在随笔阅读......
  • 基于.NetCore开发博客项目 StarBlog - (31) 发布和部署
    前言StarBlog第一期规划的功能基本完成了,我想着在春节前应该可以把第一期的系列文章完结掉,那么在差缺补漏阶段就剩下开发项目的最后一个环节——部署了。PS:事实上,还有一个很重要但又经常被略过的测试环节我们没有提到,因为时间关系,第一期规划我没有写单元测试和集成测试,在开......
  • 使用RSS+n8n同步博客园文章到cubox
    使用RSS+n8n同步博客文章到CuboxCuboxCubox是一款碎片知识文章收集的应用n8n低代码的workFlow整合大致流程定时触发器->获取RSS列表->迭代->文章是否已经同步->同步文章到cubox->同步记录写到数据库->结束这是一个大概的流程,当然也可以实现同步到其他地方的流程......
  • 学编程千万别上培训机构:费用、通用性和实战经验都不行
    学编程是一项极富挑战性的任务,而不是一件能够轻松完成的事情。很多人在学习编程的时候都会考虑去培训机构学习,然而,在现实中,并不是每个人都能从培训机构中获得真正的技术提升,相反,有许多学习编程的人,尤其是想从事IT行业的人,其实更适合采用其他的学习方式。以下是一些理有据的观点,用来......
  • 【查询类博客】LaTeX快查
    声明:本文非原创,部分借鉴互联网资源,著作权归原作者所有,仅供学习参考。公式插入方式行内公式$f(x)=a+b$$f(x)=a+b$行间公式$$f(x)=a+b$$$$f(x)=a+b$$手动编号$$f(x)=a-b\tag{1.1}$$$$f(x)=a-b\tag{1.1}$$空格名称语法预览说明两个......