【网络流】最大流问题(EK算法带模板,Dinic算法带模板及弧优化,ISAP算法带模板及弧优化)上下界网络流

本blog重点是代码

  • 网络流的相关概念
    • 流网络(flow network)
    • 流(flow)
    • 网络的流
    • 残留网络(residual network)
    • 增广路径(augmenting path)
  • Edmonds-Karp
    • 算法思想
    • bfs模板
    • 调用EK&更新残留网络流模板
    • luogu的AC代码(EK版)
  • Dinic
    • 算法思路
    • 时间复杂度证明
    • bfs模板
      • 模板1
      • 模板2
    • dfs模板
      • 不带弧优化模板(最好别用)
      • 带弧优化模板
    • 不带弧优化版AC代码
    • 带弧优化AC代码
  • ISAP
    • 算法思想
    • bfs模板
    • dfs模板
      • 不带弧优化
      • 带弧优化
    • 不带弧优化版AC代码
    • 带弧优化版AC代码
  • Update(仅模板)
    • 无源汇有上下界可行流
    • 有源汇有上下界最大流
    • 有源汇有上下界最小流

综合了自己在网上看到的众多博客,多种多样,肯定有很多问题,欢迎各位Dalao指出

网络流的相关概念

流网络(flow network)

流网络G=(V,E)G=(V, E)G=(V,E)是一个有向图,其中每条边(u,v)(u,v)(u,v)均有一非负容量c(u,v)≥0c(u, v)≥0c(u,v)0
流网络中有两个特殊的顶点: 源点s和汇点t
假定每个顶点都处于从源点到汇点的某条路径上,就是说,对每个顶点v,存在一条路径s→v→ts→v→tsvt
GGG为连通图,且∣E∣≥∣V∣−1|E| ≥|V|-1EV1

流(flow)

边的流是一个实值函数f,满足下列三个性质:
容量限制:对所有u,v∈Vu,v∈Vu,vV, 要求f(u,v)≤c(u,v)f(u, v) ≤c(u, v)f(u,v)c(u,v)
理解:流量不会超过边的容量
斜对称性:对所有u,v∈Vu,v∈Vu,vV, 要求f(u,v)=−f(v,u)f(u, v) = -f(v, u)f(u,v)=f(v,u)
理解:一个方向的流是其反方向流的相反数。这是完善的网络流理论
不可缺少的,像用正负数来定义物理量一样。
流守恒性:对所有u,v∈V−s,tu,v∈V-{s, t}u,vVs,t,要求∑vf(u,v)−∑wf(w,u)=0\sum_vf(u,v)-\sum_wf(w,u)=0vf(u,v)wf(w,u)=0
理解:进入点u的总流量=离开点u的总流量
在这里插入图片描述
每条边上斜线前的数字是流量,后的数字是容量
在这里插入图片描述

网络的流

网络的流定义为f=∑f(s,v)f=\sum f(s,v)f=f(s,v)
即,从源点s出发的总流

最大流问题就是给出一个源点和汇点的流网络,希望找到从源点到汇点的最大值流

规定f(u,v)f(u,v)f(u,v)f(v,u)f(v,u)f(v,u)最多只有一个正数(可以均为0)。若两个均为正数,可以消减一个
方向的流量
举个栗子:u给v三个苹果,v如果又给u三个苹果,那就相当于彼此之间没有给过苹果;u给v三个苹果,v给u五个苹果,就相当于v给u两个苹果。也就是说流如果都是正数,是可以相互抵消,达成协议的

残留网络(residual network)

边的残留容量:r(u,v)=c(u,v)−f(u,v)r(u,v)=c(u,v)-f(u,v)r(u,v)=c(u,v)f(u,v)
残留网络一句话就是残留边所构成的一个网络流,且每条边的容量是严格为正的,即当0<f(u,v)<c(u,v)=>r(u,v)=c(u,v)−f(u,v)>00<f(u,v)<c(u,v)=>r(u,v)=c(u,v)-f(u,v)>00<f(u,v)<c(u,v)=>r(u,v)=c(u,v)f(u,v)>0,这条边(u,v)(u,v)(u,v)仍在残留网络中
在这里插入图片描述 👉在这里插入图片描述
顶点1到2已经满流,故1到2的残留容量为0,但此时仍存在2到1的残留容量

增广路径(augmenting path)

增广路径P为残留网络中从源点s到汇点t的一条简单路径
增广路径的残留容量是:
δ(p)=minr(u,v):(u,v)∈P\delta(p)=min{r(u,v):(u,v)∈P}δ(p)=minr(u,v):(u,v)P
一句话就是这一条路径上最小的边流量
沿着路径增广是沿着路径的每条边发送δ(P)\delta(P)δ(P)单位的流。相应的,修改这一路径上的流的值和残留容量
f=f+δ(P)f=f+\delta(P)f=f+δ(P)
r(u,v)=r(u,v)−δ(P)r(u,v)=r(u,v)-\delta(P)r(u,v)=r(u,v)δ(P) (u,v)∈P(u,v)∈P(u,v)P
r(v,u)=r(v,u)+δ(P)r(v,u)=r(v,u)+\delta(P)r(v,u)=r(v,u)+δ(P) (u,v)∈P(u,v)∈P(u,v)P


接下来的各个算法介绍的模板是基于luogu这道题的AC代码,一定注意哦~~
在这里插入图片描述

Edmonds-Karp

算法思想

Edmonds–Karp算法是一种实用性很强的,实现简洁的,基于增广路思想的网络流算法。
它的基本算法思想是:每次都找长度最短的增广路径

网络图的边可看成长度均为1,因此可用BFS找从源点s到汇点t的最短路径。
为了便于算出路径的残留容量,用一个数组aug[v]aug[v]aug[v]记录从源点s到当前结点v这条路径的残留容量
在BFS时,从队首结点u扩展出邻接点v,则有:aug[v]=min(aug[u],cap[u][v])aug[v] = min(aug[u], cap[u][v])aug[v]=min(aug[u],cap[u][v])cap[u][v]cap[u][v]cap[u][v]表示(u,v)(u,v)(u,v)这一条边的残留容量
显然,BFS找出的一条增广路径的残留容量为aug[t]aug[t]aug[t]

在找到一条增广路径的过后就要更新新的残留容量
在这里插入图片描述

bfs模板

此代码中的flow即是上文中的aug

int BFS ( int s, int t ) {//使用BFS寻找增广路径 while ( ! q.empty() )q.pop();memset ( pre, -1, sizeof ( pre ) );pre[s] = 0;flow[s] = INF;//初始化源点的流量设为无穷大 q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();if ( u == t )//抵达汇点,找到了增广路径 break;for ( int i = 1;i <= t;i ++ ) {//遍历所有的点 //原图是没有流向s的流,但是在剩余网络中有反向边,故有流向s的流,所以要判断 if ( i != s && r[u][i] > 0 && pre[i] == -1 ) {pre[i] = u;//记录前驱以保存路径 flow[i] = min ( r[u][i], flow[u] );//迭代地找到增量 q.push( i );}}}if ( pre[t] == -1 )//汇点没有前驱节点,即残余图中无增广路径 return -1;return flow[t];
}

调用EK&更新残留网络流模板

int EK ( int s, int t ) {int max_flow = 0;int Flow = BFS ( s, t );while ( Flow != -1 ) {for ( int i = t;i != s;i = pre[i] ) {//利用前驱寻找路径,做到更新残留图 r[pre[i]][i] -= Flow;//改变正向边的容量 r[i][pre[i]] += Flow;//改变反向边的容量 }max_flow += Flow;Flow = BFS ( s, t ); }return max_flow;
}

luogu的AC代码(EK版)

#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define MAXN 405
#define INF 0x7f7f7f7f
queue < int > q;
int n, m;
int r[MAXN][MAXN];//记录残余网络的容量 
int flow[MAXN];//标记从源点到当前节点实际还剩多少流量 
int pre[MAXN];//节点的前驱,同时标记点是否被标记过 int BFS ( int s, int t ) {//使用BFS寻找增广路径 while ( ! q.empty() )q.pop();memset ( pre, -1, sizeof ( pre ) );pre[s] = 0;flow[s] = INF;//初始化源点的流量设为无穷大 q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();if ( u == t )//抵达汇点,找到了增广路径 break;for ( int i = 1;i <= t;i ++ ) {//遍历所有的点 //原图是没有流向s的流,但是在剩余网络中有反向边,故有流向s的流,所以要判断 if ( i != s && r[u][i] > 0 && pre[i] == -1 ) {pre[i] = u;//记录前驱以保存路径 flow[i] = min ( r[u][i], flow[u] );//迭代地找到增量 q.push( i );}}}if ( pre[t] == -1 )//汇点没有前驱节点,即残余图中无增广路径 return -1;return flow[t];
}int EK ( int s, int t ) {int max_flow = 0;int Flow = BFS ( s, t );while ( Flow != -1 ) {for ( int i = t;i != s;i = pre[i] ) {//利用前驱寻找路径,做到更新残留图 r[pre[i]][i] -= Flow;//改变正向边的容量 r[i][pre[i]] += Flow;//改变反向边的容量 }max_flow += Flow;Flow = BFS ( s, t ); }return max_flow;
}int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ ) {int si, ei, ci;scanf ( "%d %d %d", &si, &ei, &ci );r[si][ei] += ci;}printf ( "%d", EK ( 1, m ) );return 0;
}

Dinic

算法思路

1、建立网络(包括正向弧和反向弧(初始边权为0)),将总流量置为0
2、构造层次网络
层次网络,简单的说,就是求出每个点u的层次,u的层次是从源点到该点的最短路径(注意:这个最短路是指弧的权都为1的情况下的最短路),若与源点不连通,层次置为-1,只用一遍bfs即可
3、判断汇点的层次是否为-1
是:算法结束,输出当前的总流量
否:下一步
4、用一次dfs实现所有增广
增广:通过dfs找的增广路,找到了之后,将每条边的权都减去该增广路中拥有最小流量的边的流量,将每条边的反向边的权增加这个值,同时将总流量加上这个值
dfs直到找不到一条可行的从原点到汇点的路
5、重复步骤2


细节处理,如果不是用二维结构体以下标来表示边,而是一维数组以边的编号为下标
如何快速找到一条边的反向边:边的编号从0开始,反向边加在正向边之后,反向边即为该点的编号异或1
我提供的代码里是以2开始的,写法问题,别介意
复杂度:理论上来说,应该是O(n2∗m)O(n^2*m)O(n2m),n表示点数,m表示边数,然而实际上,很难卡到那个复杂度


弧优化
在DFS的时候记录当前已经计算到第几条边了,避免重复计算
然后在下一次构建层次网络的注意将head数组还原
温馨提醒:我们所说的Dinic时间复杂度是建立在运用了弧优化的基础上,所以如果你不用弧优化,这个时间复杂度就。。。
在这里插入图片描述

时间复杂度证明

与最短增广路算法一样,Dinic算法最多被分为n个阶段,每个阶段包括建层次网络和寻找增广路两部分,其中建立层次网络的复杂度仍是O(n*m)

现在来分析DFS过程的总复杂度。在每一阶段,将DFS分成两部分分析

(1)修改增广路的流量并后退的花费
在每一阶段,最多增广m次,每次修改流量的费用为O(n)O(n)O(n)。而一次增广后在增广路中后退的费用也为O(n)O(n)O(n)。所以在每一阶段中,修改增广路以及后退的复杂度为O(m∗n)O(m*n)O(mn)

(2)DFS遍历时的前进与后退
在DFS遍历时,如果当前路径的最后一个顶点能够继续扩展,则一定是沿着第i层的顶点指向第i+1层顶点的边向汇点前进了一步。因为增广路经长度最长为n,所以最坏的情况下前进n步就会遇到汇点。在前进的过程中,可能会遇到没有边能够沿着继续前进的情况,这时将路径中的最后一个点在层次图中删除

注意到每后退一次必定会删除一个顶点,所以后退的次数最多为n次。在每一阶段中,后退的复杂度为O(n)O(n)O(n)

假设在最坏的情况下,所有的点最后均被退了回来,一共共后退了n次,这也就意味着,有n次的前进被“无情”地退了回来,这n次前进操作都没有起到“寻找增广路”的作用。除去这n次前进和n次后退,其余的前进都对最后找到增广路做了贡献。增广路最多找到m次。每次最多前进n个点。所以所有前进操作最多为n+m*n次,复杂度为O(n∗m)O(n*m)O(nm)

于是得到,在每一阶段中,DFS遍历时前进与后退的花费为O(m∗n)O(m*n)O(mn)

综合以上两点,一次DFS的复杂度为O(n∗m)O(n*m)O(nm)。因此,Dinic算法的总复杂度即O(m∗n∗n)O(m*n*n)O(mnn)

bfs模板

模板1

bool bfs ( int s, int t ) {memset ( dep, 0, sizeof ( dep ) );dep[s] = 1;while ( !q.empty() )q.pop();q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = 1;i <= m;i ++ ) {if ( ! dep[i] && edge[u][i].c > edge[u][i].flow ) { dep[i] = dep[u] + 1;q.push( i );}}}return dep[t] != 0;
}

模板2

bool bfs ( int s, int t ) {memcpy ( cur, head, sizeof ( head ) );memset ( dep, 0, sizeof ( dep ) );dep[s] = 1;while ( !q.empty() )q.pop();q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].to;if ( ! dep[v] && edge[i].flow ) {//有流量就增广 dep[v] = dep[u] + 1;q.push( v );}}}return dep[t] != 0;
}

dfs模板

不带弧优化模板(最好别用)

int dfs ( int now, int t, int cap ) {if ( now == t ) return cap;int tmp = cap, flow;for ( int i = 1;i <= m && tmp;i ++ ) {if ( dep[i] == dep[now] + 1 && edge[now][i].c > edge[now][i].flow) {flow = dfs ( i, t, min ( tmp, edge[now][i].c - edge[now][i].flow ) );edge[now][i].flow += flow;edge[i][now].flow -= flow;tmp -= flow;}}return cap - tmp;
}

带弧优化模板

int dfs ( int now, int t, int cap ) {//分别是当前点,汇点,当前边上最小的流量 if ( ! cap || now == t )//终止条件,要么这条路断了,要么走到了汇点 return cap;int flow = 0;for ( int i = cur[now];i;i = edge[i].next ) {//开始弧优化 cur[now] = i;//记录一下遍历到了哪里 int v = edge[i].to;if ( dep[v] == dep[now] + 1 ) {//分层图,只能往下找一层 int tmp = dfs ( v, t, min ( cap, edge[i].flow ) );if ( ! tmp )//如果tmp=0就意味着找不到增广路了 continue;flow += tmp;cap -= tmp;edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;//处理反向边 if ( ! cap ) //没有残量就意味着没有增广路了 break;}}return flow;
}

献上我丑陋的代码。。。

不带弧优化版AC代码

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 0x7f7f7f7f
#define MAXN 205
struct node {int c, flow;
}edge[MAXN][MAXN];
queue < int > q;
int n, m, cnt = 1;
int dep[MAXN];bool bfs ( int s, int t ) {memset ( dep, 0, sizeof ( dep ) );dep[s] = 1;while ( !q.empty() )q.pop();q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = 1;i <= m;i ++ ) {if ( ! dep[i] && edge[u][i].c > edge[u][i].flow ) { dep[i] = dep[u] + 1;q.push( i );}}}return dep[t] != 0;
}int dfs ( int now, int t, int cap ) {if ( now == t ) return cap;int tmp = cap, flow;for ( int i = 1;i <= m && tmp;i ++ ) {if ( dep[i] == dep[now] + 1 && edge[now][i].c > edge[now][i].flow) {flow = dfs ( i, t, min ( tmp, edge[now][i].c - edge[now][i].flow ) );edge[now][i].flow += flow;edge[i][now].flow -= flow;tmp -= flow;}}return cap - tmp;
}int dinic ( int s, int t ) {int flow = 0;while ( bfs( s, t ) )flow += dfs ( s, t, INF );return flow;
}int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ ) {int si, ei, ci;scanf ( "%d %d %d", &si, &ei, &ci );edge[si][ei].c += ci;}printf ( "%d", dinic ( 1, m ) );return 0;
} 

带弧优化AC代码

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 0x7f7f7f7f
#define MAXN 205
struct node {int next, to, flow;
}edge[MAXN << 1];
queue < int > q;
int n, m, cnt = 1;
int head[MAXN], cur[MAXN], dep[MAXN];
//dep记录bfs分层图每个点到源点的距离 
void add ( int x, int y, int w ) {cnt ++;edge[cnt].next = head[x];edge[cnt].to = y;edge[cnt].flow = w;head[x] = cnt;
}bool bfs ( int s, int t ) {memcpy ( cur, head, sizeof ( head ) );memset ( dep, 0, sizeof ( dep ) );dep[s] = 1;while ( !q.empty() )q.pop();q.push( s );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].to;if ( ! dep[v] && edge[i].flow ) {//有流量就增广 dep[v] = dep[u] + 1;q.push( v );}}}return dep[t] != 0;
}int dfs ( int now, int t, int cap ) {//分别是当前点,汇点,当前边上最小的流量 if ( ! cap || now == t )//终止条件,要么这条路断了,要么走到了汇点 return cap;int flow = 0;for ( int i = cur[now];i;i = edge[i].next ) {//开始弧优化 cur[now] = i;//记录一下遍历到了哪里 int v = edge[i].to;if ( dep[v] == dep[now] + 1 ) {//分层图,只能往下找一层 int tmp = dfs ( v, t, min ( cap, edge[i].flow ) );if ( ! tmp )//如果tmp=0就意味着找不到增广路了 continue;flow += tmp;cap -= tmp;edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;//处理反向边 if ( ! cap ) //没有残量就意味着没有增广路了 break;}}return flow;
}int dinic ( int s, int t ) {int flow = 0;while ( bfs( s, t ) )flow += dfs ( s, t, INF );return flow;
}int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ ) {int si, ei, ci;scanf ( "%d %d %d", &si, &ei, &ci );add ( si, ei, ci );add ( ei, si, 0 );}printf ( "%d", dinic ( 1, m ) );return 0;
} 

ISAP

算法思想

我们达到如下目标:
能在O(n)时间内判断每次增广,如果我们维持“距离标号”且能在O(n)时间内实施增广.
维持和更新所有距离标号的总时间是O(mn).
总增广数是O(nm).
结论:总运行时间是O(n^2m)


距离标号
距离标号是一个函数:d:V→Z+d: V →Z+d:VZ+. 距离标号被称为是有效,如果它满足以下:
d(t)=0d(t)= 0d(t)=0 //汇点的标号为0
d(i)≤d(j)+1d(i) ≤d(j) + 1d(i)d(j)+1 对每个 (i,j)∈G(f)(i,j)∈G(f)(i,j)G(f)
(i,j)∈G(f)(i,j) ∈G(f)(i,j)G(f) 是可进入的,前提是如果 d(i)=d(j)+1d(i) =d(j) + 1d(i)=d(j)+1.
在这里插入图片描述
顶点里的数字是各顶点的距离标号。红色边是残留网络中的可进入弧。可以发现,可进入弧的数量是少于图中总的边数的,为找增广路节约了时间
在这里插入图片描述


实现
理论上初始距离标号要用反向BFS求得,实践中可以全部设为0,可以证明:这样做不改变渐进时间复杂度。
理论上可以写出许多子程序并迭代实现,但判断琐碎,没有层次,实践中用递归简单明了
GAP优化:注意到我们在某次增广后,最大流可能已经求出,因后算法继续重标号,做了许多无用功。可以发现,距离标号是单调增的。这启示我们如果标号中存在“间隙”,则图中不会再有增广路,于是算法提前终止。
实践中我们使用数组vd[i]记录标号为i的顶点个数,若重新标号使得vd中原标号项变为0,则停止算法(出现了断层) 不必在每次增广后实时更新图中的流量,可以让一条增广路有多个分叉并统计增广量,在算法前进与回溯的执行过程中自动更新流量

bfs模板

void bfs () {//初始化bfs,从t到s搜出每个点初始深度 memset ( dep, -1, sizeof ( dep ) );memset ( gap, 0, sizeof ( gap ) );while ( ! q.empty() )q.pop();dep[t] = 0;gap[0] = 1;q.push( t );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].v;if ( dep[v] != -1 )continue;q.push( v );dep[v] = dep[u] + 1;gap[dep[v]] ++;}}
}

dfs模板

不带弧优化

int dfs ( int u, int flow ) {if ( u == t ) {maxflow += flow;return flow;}int used = 0;for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].v;if ( edge[i].flow && dep[v] + 1 == dep[u] ) {//为什么不是dep[v]==dep[u]+1,请注意我们bfs是从t倒着的,自然而然dep也倒着了 int tmp = dfs ( v, min ( edge[i].flow, flow - used ) );if ( tmp ) {edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;used += tmp;}if ( used == flow )return used;}}//如果已经到此,说明该点u出去的所有点都已经流过了//并且从前面点传过来的流量flow仍有剩余//则此时要修改该点的dep使得该点与该点出去的所有点分隔开 -- gap[dep[u]];if ( ! gap[dep[u]] )//出现断层,无法到达汇点t了 dep[s] = n + 1;dep[u] ++;gap[dep[u]] ++; return used;
}

带弧优化

int dfs ( int u, int flow ) {if ( u == t ) {maxflow += flow;return flow;}int used = 0;for ( int i = cur[u];i;i = edge[i].next ) {cur[u] = i;int v = edge[i].v;if ( edge[i].flow && dep[v] + 1 == dep[u] ) {//为什么不是dep[v]==dep[u]+1,请注意我们bfs是从t倒着的,自然而然dep也倒着了 int tmp = dfs ( v, min ( edge[i].flow, flow - used ) );if ( tmp ) {edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;used += tmp;}if ( used == flow )return used;}}//如果已经到此,说明该点u出去的所有点都已经流过了//并且从前面点传过来的流量flow仍有剩余//则此时要修改该点的dep使得该点与该点出去的所有点分隔开 -- gap[dep[u]];if ( ! gap[dep[u]] )//出现断层,无法到达汇点t了 dep[s] = n + 1;dep[u] ++;gap[dep[u]] ++; return used;
}

不带弧优化版AC代码

#include <queue> 
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define MAXN 205
#define INF 0x7f7f7f7f
struct node {int v, next, flow;
}edge[MAXN << 1];
queue < int > q;
int maxflow, s, t, n, m, cnt = 1;
int dep[MAXN], gap[MAXN], head[MAXN];
//dep[i]表示节点i的深度,gap[i]表示深度为i的点的数量void add ( int x, int y, int w ) {cnt ++;edge[cnt].next = head[x];edge[cnt].flow = w;edge[cnt].v = y;head[x] = cnt;
}void bfs () {//初始化bfs,从t到s搜出每个点初始深度 memset ( dep, -1, sizeof ( dep ) );memset ( gap, 0, sizeof ( gap ) );while ( ! q.empty() )q.pop();dep[t] = 0;gap[0] = 1;q.push( t );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].v;if ( dep[v] != -1 )continue;q.push( v );dep[v] = dep[u] + 1;gap[dep[v]] ++;}}
}
//可以看出ISAP的bfs里面对边权!=0的条件没有限制,只需要再后面的dfs里进行判断即可
int dfs ( int u, int flow ) {if ( u == t ) {maxflow += flow;return flow;}int used = 0;for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].v;if ( edge[i].flow && dep[v] + 1 == dep[u] ) {//为什么不是dep[v]==dep[u]+1,请注意我们bfs是从t倒着的,自然而然dep也倒着了 int tmp = dfs ( v, min ( edge[i].flow, flow - used ) );if ( tmp ) {edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;used += tmp;}if ( used == flow )return used;}}//如果已经到此,说明该点u出去的所有点都已经流过了//并且从前面点传过来的流量flow仍有剩余//则此时要修改该点的dep使得该点与该点出去的所有点分隔开 -- gap[dep[u]];if ( ! gap[dep[u]] )//出现断层,无法到达汇点t了 dep[s] = n + 1;dep[u] ++;gap[dep[u]] ++; return used;
}int ISAP () {maxflow = 0;bfs();while ( dep[s] < n )dfs ( s, INF );return maxflow;
}int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ ) {int si, ei, ci;scanf ( "%d %d %d", &si, &ei, &ci );add ( si, ei, ci );add ( ei, si, 0 );}s = 1;t = m;printf ( "%d", ISAP() );return 0;
} 

带弧优化版AC代码

如果已经懂了Dinic,其实只要改一点点就好了

#include <queue> 
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define MAXN 205
#define INF 0x7f7f7f7f
struct node {int v, next, flow;
}edge[MAXN << 1];
queue < int > q;
int maxflow, s, t, n, m, cnt = 1;
int dep[MAXN], gap[MAXN], head[MAXN], cur[MAXN];
//dep[i]表示节点i的深度,gap[i]表示深度为i的点的数量void add ( int x, int y, int w ) {cnt ++;edge[cnt].next = head[x];edge[cnt].flow = w;edge[cnt].v = y;head[x] = cnt;
}void bfs () {//初始化bfs,从t到s搜出每个点初始深度 memset ( dep, -1, sizeof ( dep ) );memset ( gap, 0, sizeof ( gap ) );while ( ! q.empty() )q.pop();dep[t] = 0;gap[0] = 1;q.push( t );while ( ! q.empty() ) {int u = q.front();q.pop();for ( int i = head[u];i;i = edge[i].next ) {int v = edge[i].v;if ( dep[v] != -1 )continue;q.push( v );dep[v] = dep[u] + 1;gap[dep[v]] ++;}}
}
//可以看出ISAP的bfs里面对边权!=0的条件没有限制,只需要再后面的dfs里进行判断即可
int dfs ( int u, int flow ) {if ( u == t ) {maxflow += flow;return flow;}int used = 0;for ( int i = cur[u];i;i = edge[i].next ) {cur[u] = i;int v = edge[i].v;if ( edge[i].flow && dep[v] + 1 == dep[u] ) {//为什么不是dep[v]==dep[u]+1,请注意我们bfs是从t倒着的,自然而然dep也倒着了 int tmp = dfs ( v, min ( edge[i].flow, flow - used ) );if ( tmp ) {edge[i].flow -= tmp;edge[i ^ 1].flow += tmp;used += tmp;}if ( used == flow )return used;}}//如果已经到此,说明该点u出去的所有点都已经流过了//并且从前面点传过来的流量flow仍有剩余//则此时要修改该点的dep使得该点与该点出去的所有点分隔开 -- gap[dep[u]];if ( ! gap[dep[u]] )//出现断层,无法到达汇点t了 dep[s] = n + 1;dep[u] ++;gap[dep[u]] ++; return used;
}int ISAP () {maxflow = 0;bfs();while ( dep[s] < n ) {memcpy ( cur, head, sizeof ( head ) ); dfs ( s, INF );}return maxflow;
}int main() {scanf ( "%d %d", &n, &m );for ( int i = 1;i <= n;i ++ ) {int si, ei, ci;scanf ( "%d %d %d", &si, &ei, &ci );add ( si, ei, ci );add ( ei, si, 0 );}s = 1;t = m;printf ( "%d", ISAP() );return 0;
} 

肯定有很多问题,欢迎各位Dalao指出

Update(仅模板)

无源汇有上下界可行流

//LOJ 题目同名
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 205
#define inf 1e9
struct node {int nxt, to, flow;
}edge[maxn * maxn];
queue < int > q;
int n, m, cnt;
int dep[maxn], head[maxn], cur[maxn], d[maxn], minn[maxn * maxn];void addedge( int u, int v, int w ) {edge[cnt].nxt = head[u];edge[cnt].to = v;edge[cnt].flow = w;head[u] = cnt ++;edge[cnt].nxt = head[v];edge[cnt].to = u;edge[cnt].flow = 0;head[v] = cnt ++;
}bool bfs( int s, int t ) {memcpy( cur, head, sizeof( head ) );memset( dep, 0, sizeof( dep ) );dep[s] = 1, q.push( s );while( ! q.empty() ) {int u = q.front(); q.pop();for( int i = head[u];~ i;i = edge[i].nxt ) {int v = edge[i].to;if( ! dep[v] && edge[i].flow ) {dep[v] = dep[u] + 1;q.push( v );}}}return dep[t];
}int dfs( int u, int t, int cap ) {if( ! cap || u == t ) return cap;int flow = 0;for( int i = cur[u];~ i;i = edge[i].nxt ) {cur[u] = i;int v = edge[i].to;if( dep[v] == dep[u] + 1 ) {int w = dfs( v, t, min( cap, edge[i].flow ) );if( ! w ) continue;cap -= w;flow += w;edge[i].flow -= w;edge[i ^ 1].flow += w;if( ! cap ) break;}}return flow;
}int dinic( int s, int t ) {int ans = 0;while( bfs( s, t ) )ans += dfs( s, t, inf );return ans;
}int main() {memset( head, -1, sizeof( head ) );scanf( "%d %d", &n, &m );int s = 0, t = n + 1, sum = 0;for( int i = 0, u, v, low, up;i < m;i ++ ) {scanf( "%d %d %d %d", &u, &v, &low, &up );d[u] -= low;d[v] += low;minn[i] = low;addedge( u, v, up - low );}for( int i = 1;i <= n;i ++ ) {if( d[i] > 0 ) {sum += d[i];addedge( s, i, d[i] );}if( d[i] < 0 )addedge( i, t, -d[i] );}if( sum != dinic( s, t ) ) printf( "NO\n" );else {printf( "YES\n" );for( int i = 0;i < m;i ++ )printf( "%d\n", edge[i << 1 | 1].flow + minn[i] );}return 0;
}
//无源汇有上下界可行流

有源汇有上下界最大流

//LOJ 题目同名
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 205
#define inf 1e9
struct node {int nxt, to, flow;
}edge[maxn * maxn];
queue < int > q;
int cnt;
int head[maxn], cur[maxn], dep[maxn], d[maxn];void addedge( int u, int v, int w ) {edge[cnt].nxt = head[u];edge[cnt].to = v;edge[cnt].flow = w;head[u] = cnt ++;edge[cnt].nxt = head[v];edge[cnt].to = u;edge[cnt].flow = 0;head[v] = cnt ++;
}bool bfs( int s, int t ) {memcpy( cur, head, sizeof( head ) );memset( dep, 0, sizeof( dep ) );dep[s] = 1, q.push( s );while( ! q.empty() ) {int u = q.front(); q.pop();for( int i = head[u];~ i;i = edge[i].nxt ) {int v = edge[i].to;if( ! dep[v] && edge[i].flow ) {dep[v] = dep[u] + 1;q.push( v );}}}return dep[t];
}int dfs( int u, int t, int cap ) {if( ! cap || u == t ) return cap;int flow = 0;for( int i = cur[u];~ i;i = edge[i].nxt ) {cur[u] = i;int v = edge[i].to;if( dep[v] == dep[u] + 1 ) {int w = dfs( v, t, min( cap, edge[i].flow ) );if( ! w ) continue;cap -= w;flow += w;edge[i].flow -= w;edge[i ^ 1].flow += w;if( ! cap ) break;}}return flow;
}int dinic( int s, int t ) {int ans = 0;while( bfs( s, t ) )ans += dfs( s, t, inf );return ans;
}int main() {memset( head, -1, sizeof( head ) );int n, m, s, t;scanf( "%d %d %d %d", &n, &m, &s, &t );int ss = 0, tt = n + 1, sum = 0;for( int i = 1, u, v, low, up;i <= m;i ++ ) {scanf( "%d %d %d %d", &u, &v, &low, &up );d[u] -= low, d[v] += low;addedge( u, v, up - low );}for( int i = 1;i <= n;i ++ ) {if( d[i] > 0 ) {sum += d[i];addedge( ss, i, d[i] );}if( d[i] < 0 )addedge( i, tt, -d[i] );}addedge( t, s, inf );if( sum != dinic( ss, tt ) ) return ! printf( "please go home to sleep\n" );else printf( "%d\n", dinic( s, t ) );return 0;
}
//有源汇有上下界最大流 

有源汇有上下界最小流

//LOJ 题目同名
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define int long long
#define maxn 50010
#define inf 1e18
#define maxm 400100
struct node {int nxt, to, flow;
}edge[maxm];
queue < int > q;
int cnt;
int head[maxn], cur[maxn], dep[maxn], d[maxn];void addedge( int u, int v, int w ) {edge[cnt].nxt = head[u];edge[cnt].to = v;edge[cnt].flow = w;head[u] = cnt ++;edge[cnt].nxt = head[v];edge[cnt].to = u;edge[cnt].flow = 0;head[v] = cnt ++;
}bool bfs( int s, int t ) {memcpy( cur, head, sizeof( head ) );memset( dep, 0, sizeof( dep ) );dep[s] = 1, q.push( s );while( ! q.empty() ) {int u = q.front(); q.pop();for( int i = head[u];~ i;i = edge[i].nxt ) {int v = edge[i].to;if( ! dep[v] && edge[i].flow ) {dep[v] = dep[u] + 1;q.push( v );}}}return dep[t];
}int dfs( int u, int t, int cap ) {if( ! cap || u == t ) return cap;int flow = 0;for( int i = cur[u];~ i;i = edge[i].nxt ) {cur[u] = i;int v = edge[i].to;if( dep[v] == dep[u] + 1 ) {int w = dfs( v, t, min( cap, edge[i].flow ) );if( ! w ) continue;cap -= w;flow += w;edge[i].flow -= w;edge[i ^ 1].flow += w;if( ! cap ) break;}}return flow;
}int dinic( int s, int t ) {int ans = 0;while( bfs( s, t ) )ans += dfs( s, t, inf );return ans;
}signed main() {memset( head, -1, sizeof( head ) );int n, m, s, t;scanf( "%lld %lld %lld %lld", &n, &m, &s, &t );int ss = 0, tt = n + 1, sum = 0;for( int i = 1, u, v, low, up;i <= m;i ++ ) {scanf( "%lld %lld %lld %lld", &u, &v, &low, &up );d[u] -= low, d[v] += low;addedge( u, v, up - low );}for( int i = 1;i <= n;i ++ ) {if( d[i] > 0 ) {sum += d[i];addedge( ss, i, d[i] );}if( d[i] < 0 ) addedge( i, tt, -d[i] );}int flow = 0;flow += dinic( ss, tt );addedge( t, s, inf );flow += dinic( ss, tt );if( sum != flow ) return ! printf( "please go home to sleep\n" );else printf( "%lld\n", edge[cnt - 1].flow );return 0;
}
//有源汇有上下界最小流

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318098.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Rainbond 5.0正式发布, 支持对接管理已有Kubernetes集群

今天很高兴的向大家宣布Rainbond v5.0正式发布&#xff0c;Rainbond是开源的企业应用云操作系统&#xff0c;支撑企业应用的开发、架构、交付和运维的全流程&#xff0c;通过无侵入架构&#xff0c;无缝衔接各类企业应用&#xff0c;底层资源可以对接和管理IaaS、虚拟机和物理服…

Fibonacci

Fibonacci 题意&#xff1a; f[i]表示第i位的斐波那契数列 给定n&#xff0c;求 题解&#xff1a; 这种题一开始没什么思路&#xff0c;那么枚举就行 g(x,y) 1 是当x * y为偶数时 x * y为偶数说明&#xff1a; x是偶数&#xff0c;y也是偶数 x是奇数&#xff0c;y是偶数 而…

基于.NET Standard的分布式自增ID算法--美团点评LeafSegment

概述前一篇文章讲述了最流行的分布式ID生成算法snowflake&#xff0c;本篇文章根据美团点评分布式ID生成系统文章&#xff0c;介绍另一种相对更容易理解和编写的分布式ID生成方式。实现原理Leaf这个名字是来自德国哲学家、数学家莱布尼茨的一句话&#xff1a;There are no two …

[费用流专题]Going Home,Minimum Cost,工作安排

文章目录T1&#xff1a;Going Home题目题解CODET2&#xff1a;Minimum Cost题目题解CODET3&#xff1a;工作安排题解CODET1&#xff1a;Going Home 题目 On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, e…

Sky Garden

Sky Garden 题意&#xff1a; 画n个圆和m条直线&#xff0c;圆的中心点为(0,0)&#xff0c;圆的半径分别从1到n&#xff0c;而直线都必经过(0,0)点&#xff0c;并且所有直线会把每个圆平均分成2m个面积相等的区域&#xff0c;直线会和圆形成交点&#xff0c;求所有交点两两经…

IdentityServer4-前后端分离的授权验证(六)

上两节介绍完Hybrid模式在MVC下的使用&#xff0c;包括验证从数据获取的User和Claim对MVC的身份授权。本节将介绍Implicit模式在JavaScript应用程序中的使用&#xff0c;使用Node.jsExpress构建JavaScript客户端&#xff0c;实现前后端分离。本节授权服务和资源服务器基于第四和…

人类智慧贪心

题意看起来很清新&#xff0c;代码实现也基本在入门难度&#xff0c;但是为什么我不会&#xff01; 另&#xff1a;反悔贪心 <details><summary>$\texttt{solution}$</summary></details> P2672 [NOIP2015 普及组] 推销员 $\texttt{solution}$ 发现答案…

周末狂欢赛3(跳格子,英雄联盟,排序问题)

文章目录T1&#xff1a;跳格子题目题解CODET2&#xff1a;英雄联盟题目题解CODET3&#xff1a;排序问题题目题解CODET1&#xff1a;跳格子 题目 n 个格子排成一列&#xff0c;一开始&#xff0c;你在第一个格子&#xff0c;目标为跳到第 n 个格子。在每个格子 i 里面你可以做…

想让AI在企业落地?微软最新Azure AI不容错过!

Microsoft Connect(); 2018 如期举行&#xff0c;大会上发布的众多顶尖技术&#xff0c;瞬间引爆了全球&#xff01;AI的高速发展&#xff0c;正在掀起新一波的创新浪潮。对于很多企业来说&#xff0c;AI创造的巨大价值&#xff0c;是不容错过的风口&#xff0c;大会上&#xf…

[费用流]数字配对,新生舞会

文章目录T1&#xff1a;数字配对题目题解CODET2&#xff1a;新生舞会题目题解CODE&#xff08;最大费用最大流版&#xff09;CODE&#xff08;最小费用最大流版&#xff09;T1&#xff1a;数字配对 题目 有 n 种数字&#xff0c;第 i 种数字是 ai、有 bi 个&#xff0c;权值是…

.NET Core实战项目之CMS 第十三章 开发篇-在MVC项目结构介绍及应用第三方UI

作为后端开发的我来说&#xff0c;前端表示真心玩不转&#xff0c;你如果让我微调一个位置的样式的话还行&#xff0c;但是让我写一个很漂亮的后台的话&#xff0c;真心做不到&#xff0c;所以我一般会选择套用一些开源UI模板来进行系统UI的设计。那如何套用呢&#xff1f;今天…

[FFT/IFFT]快速傅里叶(逆)变化 + 递归和递推模板

现在时间是2021-2-2&#xff0c;重新回来看2019学习的一知半解的FFTFFTFFT&#xff0c;又有了新的理解 所以修改了以往写过的文章&#xff0c;并增添些许内容 因为过去一年多&#xff0c;上了高中&#xff0c;学的知识多了些&#xff0c;以前不懂的有些东西现在看来挺简单的&am…

软件开发模式:瀑布与敏捷

瀑布和敏捷不是什么新概念&#xff0c;这里只是个人在团队合作中不得不去思考而做的归纳和总结&#xff0c;同时记录自己曾经踩过的坑&#xff0c;新瓶装旧酒&#xff0c;希望对你有所启发。瀑布模式瀑布模型是比较传统一种开发模式&#xff0c;特别是在2B的传统企业&#xff0…

.net core+Spring Cloud学习之路 一

文章开头唠叨两句。2019年了&#xff0c;而自己参加工作也两年有余了&#xff0c;用一个词来概括这两年多的生活&#xff0c;就是&#xff1a;“碌碌无为”。也不能说一点收获都没有&#xff0c;但是很少。2019来了&#xff0c;我立志要打破现状&#xff0c;改变自己&#xff0…

P3128 [USACO15DEC]Max Flow P

P3128 [USACO15DEC]Max Flow P 树上差分之点差分模板题 题目描述&#xff1a; FJ给他的牛棚的N(2≤N≤50,000)个隔间之间安装了N-1根管道&#xff0c;隔间编号从1到N。所有隔间都被管道连通了。 FJ有K(1≤K≤100,000)条运输牛奶的路线&#xff0c;第i条路线从隔间si运输到隔…

周末狂欢赛4(1-02E. JM的西伯利亚特快专递,寿司晚宴,荷马史诗)

文章目录T1&#xff1a;1-02E. JM的西伯利亚特快专递题目题解codeT2&#xff1a;寿司晚宴题目题解codeT3&#xff1a;荷马史诗题目题解codeT1&#xff1a;1-02E. JM的西伯利亚特快专递 题目 今天JM收到了一份来自西伯利亚的特快专递&#xff0c;里面装了一个字符串 s &#x…

.NET Core容器化开发系列(一)——Docker里面跑个.NET Core

前言博客园中已经有很多如何在Docker里面运行ASP.NET Core的介绍了。本篇主要介绍一些细节&#xff0c;帮助初学的朋友更加深入地理解如何在Docker中运行ASP.NET Core。安装DockerDocker现支持在主流Linux、Windows和macOS上安装&#xff0c;官方的安装文档请参考docker docs。…

中小研发团队架构实践之生产环境诊断工具WinDbg

生产环境偶尔会出现一些异常问题&#xff0c;WinDbg或GDB是解决此类问题的利器。调试工具WinDbg如同医生的听诊器&#xff0c;是系统生病时做问题诊断的逆向分析工具&#xff0c;Dump文件类似于飞机的黑匣子&#xff0c;记录着生产环境程序运行的状态。本文主要介绍了调试工具W…

多项式的基础操作(逆元/除法/取模/对数ln/开根sqrt/指数exp/快速幂)带模板+luogu全套例题

文章目录多项式的逆元理论推导模板例题&#xff1a;[luogu P4238]【模板】多项式乘法逆题目code多项式的除法/取模理论推导多项式牛顿迭代法模板例题&#xff1a;[luoguP4512]【模板】多项式除法题目code多项式对数ln理论推导模板例题题目code多项式开根sqrt理论推导模板例题题…

从软件工程的角度解读任正非的新年公开信

昨天被任正非的那封《全面提升软件工程能力与实践&#xff0c;打造可信的高质量产品》的公开信刷屏了&#xff0c;作为一个软件工程专业科班出身的软件开发从业者&#xff0c;自然是引起了我&#xff08;宝玉xp&#xff09;的好奇&#xff0c;仔细阅读之下确实让我大吃一惊&…