首页 > 其他分享 >浅谈EK求网络流 & 最小费用最大流

浅谈EK求网络流 & 最小费用最大流

时间:2024-02-28 22:03:23浏览次数:189  
标签:浅谈 EK int 路径 最小 流量 st val id

1.简介:

网络流,指的是一种图上问题。首先我们要知道什么是网络。

网络的性质如下:

  1. 有且仅有一个点入度为 0,且只有一个点出度为0,我们把入读为 0 的点叫做源点,出度为 0 的点为汇点。

  2. 网络是一个有向图,且有边权。

那么流是什么呢?

考虑对于下面这个网络:

其中 \(s\) 是源点,\(t\) 是汇点。

我们把一条边看成一个水管,边权就是水管的容量,那么一条从 \(s\) 到 \(t\) 的路径就是一条流,而这条流的流量就是这条路径流量最小的那条边。

对于最大流问题,我们要求的其实是所有 \(s\) 到 \(t\) 的所有路径的流量的总和最大是多少。

PS:一条边可以被多条路径经过,但每次流过的水量的总和不能超过其边权。

还有一个定义:增广路,虽然这个名字很高级,但其实就是指一条 \(s\) 到 \(t\) 的流。。。。

2.最大流:

模板题

知道了最大流的定义,我们很显然就可以想到一种暴力算法。
直接从 \(s\) 开始爆搜出每一条路径,到达 \(t\) 后把所有经过的边减去流量,然后答案加上流量。

但这样就有了一个问题:第一次找到的路径径不一定是最优的。

怎么理解这句话呢? 如图:

我们考虑在这张图上跑网络流。

显然,最优情况是这样的:

这种路径答案为 2(蓝色路径是最优路径)

但是用上述算法可能会出现这种情况:

这种情况答案为 1。

所以我们要避免第二种情况。

首先想到的肯定是遇到这种路径时尝试撤销这条流,然后回复该边的流量,但这样时间会爆。

这时要用一个很神奇的东西----反向边。

对于每条边我们建一个反向边。
初始时,这条边的容量为 0,代表着所对应的正向边减少了 0,然后每次正向边减少容量反向边就增加容量。

考虑为什么加上反向边后就可行了。

其实我也不会。

我们可以感性理解一下:

如图,一条蓝色的流在过程中遇到了红色的流:

如果没有反向边,就不能流了。
但有了反向边,就可以把红色的流撤回去,如图:

就得到了两条流。

链接生活实际我们可以知道:

其实反向边就相当于这条水管反着流来了 \(x\) 的水,那么原来正向水管中的流量就减少了 \(x\)。

再观察上图我们可以发现:

其实蓝色和红色的路径就相当于下图中的路径:

所以这个算法是正确的。

那么我们再捋一下思路;

  1. 用 BFS 或 DFS 随便搜一条 \(s\) 到 \(t\) 的路径。

  2. 把路径上的所有值都减去当前路径能经过的最大流量,并把每条边的反向边增加最大流量,答案也增加。

  3. 重复 1 和 2 操作。

这样就可以求出最大流了。

CODE(BFS):

struct D {
  int pre, id;  // 从pre节点来的,经过的id边
} p[kMaxN];

long long q[kMaxN], vis[kMaxN], n, m, s, t, ans;
int nxt[kMaxM], to[kMaxM], val[kMaxM], h[kMaxM], st = 1;

void add(int u, int v, int w) {  //  链式前向星建图
  ++st, nxt[st] = h[u], to[st] = v, val[st] = w, h[u] = st;
}

bool bfs() {  //  bfs 找路径
  int l = 1, r = 0;
  for (int i = 1; i <= n; i++) {
    vis[i] = 0;
    p[i] = {-1, -1};
  }
  for (q[++r] = s, vis[s] = 1; l <= r; l++) {
    int x = q[l];
    if (x == t) {
      return 1;
    } else {
      for (int i = h[x]; i; i = nxt[i]) {
        int v = to[i];
        if (!vis[v] && val[i] > 0) {
          vis[v] = 1;
          q[++r] = v;
          p[v] = {x, i};  //  记录是从哪里来的
        }
      }
    }
  }
  return 0;
}

int EK() {
  while (bfs()) {  //  能够找到增广路
    int x = t, y = 1e9;
    for (; x != s; x = p[x].pre) {  //  找到瓶颈
      y = min(y, val[p[x].id]);
    }
    x = t;
    for (; x != s; x = p[x].pre) {  //  改变流量
      val[p[x].id] -= y, val[p[x].id ^ 1] += y;
    }
    ans += y;
  }
  return ans;
}

DFS:

struct D {
  int pre, id;  // 从pre节点来的,经过的id边
} p[kMaxN];

long long q[kMaxN], vis[kMaxN], n, m, s, t, ans;
int nxt[kMaxM], to[kMaxM], val[kMaxM], h[kMaxM], st = 1;

void add(int u, int v, int w) {  //  链式前向星建图
  ++st, nxt[st] = h[u], to[st] = v, val[st] = w, h[u] = st;
}

bool dfs(int u) {
  if (u == t) {
    return 1;
  }
  bool vs = 0;
  for (int i = h[u]; i; i = nxt[i]) {
    int v = to[i];
    if (val[i] > 0 && !vis[v]) {
      vis[v] = 1;
      p[v] = {u, i};
      vs |= dfs(v);
    }
  }
  return vs;
}


int EK() {
  while (dfs(s)) {  //  能够找到增广路
    int x = t, y = 1e9;
    for (; x != s; x = p[x].pre) {  //  找到瓶颈
      y = min(y, val[p[x].id]);
    }
    x = t;
    for (; x != s; x = p[x].pre) {  //  改变流量
      val[p[x].id] -= y, val[p[x].id ^ 1] += y;
    }
    ans += y;
    fill(vis + 1, vis + n + 1, 0);
  }
  return ans;
}

这里有个小技巧。存边的时候最好是用链式前向星存,然后从 2 开始编号,这样就可以用编号异或 1 来找到反向边了。

还有就是一般建议写广搜(但是深搜也要练,因为 Dinic 要用),因为广搜会自动找到最短的一条路径,减少退流的次数。

最小费用最大流:

模板题

顾名思义,这个问题让你求解的就是在流量最大的情况下找到费用最小的方案。

那么我们该如何选择路径才能让费用最小呢?

我们既然要求费用最小,也就是边权,那么自然而然就可以想到 我们要求出原图的最短路

对于正的边,其边权就是费用,但 反向边的边权是费用的相反数。为什么呢?

我们可以再次感性理解一下。

我们把一个点的流量通过花费一定费用到另一个点看作是一个人花钱买了票从一个地方到另一个地方。
而反向边就想到与这个人不想去那个地方了,所以要退票,也就是加上 \(-w\) 元。

所以我们把在网络流找路径的时候用的 BFS 或 DFS 换成 SPFA (Dijkstra 解决不了负边权,但是好像有人用势能 Dijkstra 过了(? ),然后按照最大流的步骤来就好了。

网络流的基础技巧之---拆点

拆点是一种很常见但上限很高的技巧,可以把很多复杂问题转换为网络流问题。

举个栗子

首先我们很容易想到把第 \(i\) 个人和第 \(j\) 个工作之间连一条流量为 1(代表只能一个人只能做一次这个工作),费用为 \(c_{i,j}\) 的边,再让源点向所有人连边,工作向汇点连边,然后在上面跑最小费用流和最大费用流就好了。

但是这样会有一个问题 每个人只能做一个工作,且一个工作只能被一个人做,但如果用上述做法就可能无法满足。

那么我们可以把所有的人和工作都拆成一个入点和一个出点,再在入点和出点间连一个费用为 0 且流量为 1 的边即可。

标签:浅谈,EK,int,路径,最小,流量,st,val,id
From: https://www.cnblogs.com/caoshurui/p/18042019

相关文章

  • PDF文件太大如何免费压缩到最小?
    又到了一年一度找工作高峰期了,为了防止发送的简历文档排版错乱,一般都是采用PDF格式。但有时在投递简历时,附上过大的附件(比如设计岗位),这样就会影响发送和对方打开查看的效率。那么pdf如何快速免费压缩大小呢?教你2个无需安装软件的简单方法。方法一:操作系统自带压缩功能这是一种......
  • Eureka源码分析
     注册中心1、Register:服务注册  当Eureka客户端向EurekaServer注册时,它提供自身的元数据,比如IP地址、端口,运行状况指示符URL,主页等    1.1、服务端注册    会拉取配置的注册中心地址,向附近注册服务注册    1.2、客户端注册客户端第一次续约会失败,因......
  • jekyll安装相关,ruby rvm安装记录
    一、安装#安装jekllyhttps://dandelioncloud.cn/article/details/1524755285050953730/配置、添加源:gemsources--addhttps://gems.ruby-china.com/--removehttps://rubygems.org/gemsources-lgemsources-u安装jeklly,安装命令:(sudo命令无)geminstalljekyllbundl......
  • 76. 最小覆盖子串C
    inthash(charc){returnc-'A'+1;}booljudge_Same(inta[],intb[]){for(inti=0;i<200;i++){if(b[i]!=0&&b[i]>a[i])returnfalse;}returntrue;}char*minWindow(char*s,char*t){intns=strlen......
  • 浅谈Nacos
    主要功能和特点服务发现与注册:Nacos允许服务实例动态地注册和发现,当新的服务实例上线或下线时,注册中心能够实时感知并通知其他服务。服务中心原理动态配置管理:Nacos提供了一个集中化的配置管理平台,可以动态地管理和推送应用程序的配置,实现配置的实时更新和回滚。注册中心原......
  • 209. 长度最小的子数组C
    滑动窗口的妙用!!intminSubArrayLen(inttarget,int*nums,intnumsSize){intsum=nums[0];//区间head到tail的和inthead=0,tail=0;intminn=numsSize;inttag=0;if(numsSize==0)return-1;while(head<=tail&&tail<numsSize){......
  • P6805 (树上最小路径覆盖思想)
    难度3比较有意思的一道题。首先看到题目是求树上最小路径覆盖,自然想到由叶子入手去分析,所以当子树内有偶数个叶子节点时,那么就有两个叶子向上匹配,否则只有一个叶子向上匹配。这个东西可以用树剖维护,线段树维护的是子树内有多少个偶的,相当于一个区间反转一类的东西。细节不算多,但......
  • Leetcode 76. 最小覆盖子串
    题目描述(难度hard)给你一个字符串S、一个字符串T,请在字符串S里面找出:包含T所有字母的最小子串。示例:输入:S="ADOBECODEBANC",T="ABC"输出:"BANC"说明:如果S中不存这样的子串,则返回空字符串""。如果S中存在这样的子串,我们保证它是唯一的答案。解题思路......
  • R语言中实现广义相加模型GAM和普通最小二乘(OLS)回归
    原文链接:http://tecdat.cn/?p=20882 原文出处:拓端数据部落公众号 1导言这篇文章探讨了为什么使用广义相加模型 是一个不错的选择。为此,我们首先需要看一下线性回归,看看为什么在某些情况下它可能不是最佳选择。 2回归模型假设我们有一些带有两个属性Y和X的数据。如果它......
  • winter week6 day1
    2024蓝桥杯模拟赛3(div1+div2)A[传智杯#3决赛]序列思路:暴力枚举查看代码#include<bits/stdc++.h>usingnamespacestd;#defineintlonglong//#defineint__int128#definedoublelongdoubletypedefpair<int,int>PII;typedefpair<string,int>PSI;typedefpa......