最大流(网络流)基础篇(剪辑)
查看资料:lrj 《算法竞赛入门经典》
相关概念:
最大流:(Maximum-Flow Problem)
从源点 S 中间经过一些点,一些的物品运送到汇点 t 。
中途每两点间都有个最大运送物品数。
求从 s 到 t 最多能运送多少物品。
容量: 对于一条边 (u,v),它的物品上限(能够运送的物品最大数量)称为容量 (capacity),
记为 c(u,v) (对于不存在的边 (u,v) , c(u,v) = 0)
流量: 实际运送物品数称为流量 (flow)
规定:f(u,v) 和 f(v,u) 最多只有一个正数(可以均为 0),且 f(u,v) = - f(v,u)
PS:此图左边表示实际运送物品,右边表示最大容量。
结论:对于除了 s 和 t 的任意节点 u, ∑ f(u,v) = 0 (有些 f 为负数) 。
(u,v)∈E
最大流问题中: 容量 c 和 流量 f 满足三个性质
容量限制 f(u,v) <= c(u,v)
斜对称:f(u, v) = -f(u,v)
流量平衡:对于除了 s 和 t 的任意节点 u, ∑ f(u,v) = 0 (有些 f 为负数) 。
(u,v)∈E
目标:最大化 | f | = ∑ f(s,v) = ∑ f(u,t) 即从 S 点流出的净流量(=流入 t 点的净流量)
(s,v)∈E , (u,t)∈E
增广路算法:
残量:上图中每条边上的容量差 (称为残余流量,简称残量),
比如说上面第二个图中 V2 到 V4 残量为 14-11 = 3; V4 到 V2 残量为 0-(-11)= 11
算法基于事实:
残量网络中任何一个从 s 到 t 的有向道路都对应一条原图中的增广路【PS:不理解这个名词也没事继续看】。
只要求出该道路中所有残量的最小值 d,把对应的所有边上的流量增加 d 即可,这个过程称为增广。
也就是说只要有从起点 s 到终点 t 的路上存在流量,那么找出最小的残余流量 d
那么这个 d 肯定是满足这条路径的每一条边的,否则找不出这样的 d
那么这条路径上的每一条边的流量增加 d ,总流量增加 d 就好了。
然后继续找,直到找不到为止。
不难证明如果增广前的流量满足 3 个条件,那么增广之后任然满足。
显然只要残量网中存在增广路,流量就可以增大。
逆命题:如果残量网中不存在增广路,则当前流就是最大流,这就是著名的增广路定理。
问题:如何找路径? DFS ms 很慢,用 BFS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | queue< int > q; memset(flow,0, sizeof (flow)); //初始化流量为 0 f = 0; // 初始化总流量为 0 for (;;) //BFS 找增广路 { memset(a,0, sizeof (a)); // a[i]:从起点 s 到 i 的最小残量【每次for()时 a[] 重新清 0 因此同时可做标记数组 vis】 a[s] = INF; //起点残量无线大 q.push(s); //起点入队 while (!q.empty()) // BFS 找增广路 { int u = q.front(); //取队首 q.pop(); // 出队 for ( int v = 1; v <= n; v++) if (!a[v] && cap[u][v] > flow[u][v]) //找新节点 v { p[v] = u; q.push(v); //记录 v 的父亲节点,并加入 FIFO 队列 a[v] = min(a[u], cap[u][v]-flow[u][v]); // s-v 路径上的最小残量【从而保证了最后,每条路都满足a[t]】 } } if (a[t] == 0) break ; // 找不到,则当前流已经是最大流 【t为终点】 for ( int u = t; u != s; u = p[u]) // 从汇点往回走 { flow[p[u]][u] += a[t]; // 更新正向流 flow[u][p[u]] -= a[t]; // 更新反向流 } f += a[t]; // 更新从 S 流出的总流量 } |
hdu 3549 Flow Problem【最大流增广路入门模板题】
最大流模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | const int MAXN=20010; //点数的最大值 const int MAXM=880010; //边数的最大值 const int INF=0x3f3f3f3f; struct Node { int from ,to,next; int cap; }edge[MAXM]; int tol; int head[MAXN]; int dep[MAXN]; int gap[MAXN]; //gap[x]=y :说明残留网络中dep[i]==x的个数为y int n; //n是总的点的个数,包括源点和汇点 void init() { tol=0; memset(head,-1, sizeof (head)); } void addedge( int u, int v, int w) { edge[tol]. from =u; edge[tol].to=v; edge[tol].cap=w; edge[tol].next=head[u]; head[u]=tol++; edge[tol]. from =v; edge[tol].to=u; edge[tol].cap=0; edge[tol].next=head[v]; head[v]=tol++; } void BFS( int start, int end) { memset(dep,-1, sizeof (dep)); memset(gap,0, sizeof (gap)); gap[0]=1; int que[MAXN]; int front,rear; front=rear=0; dep[end]=0; que[rear++]=end; while (front!=rear) { int u=que[front++]; if (front==MAXN)front=0; for ( int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].to; if (edge[i].cap!=0||dep[v]!=-1) continue ; que[rear++]=v; if (rear==MAXN)rear=0; dep[v]=dep[u]+1; ++gap[dep[v]]; } } } int SAP( int start, int end) { int res=0; BFS(start,end); int cur[MAXN]; int S[MAXN]; int top=0; memcpy(cur,head, sizeof (head)); int u=start; int i; while (dep[start]<n) { if (u==end) { int temp=INF; int inser; for (i=0;i<top;i++) if (temp>edge[S[i]].cap) { temp=edge[S[i]].cap; inser=i; } for (i=0;i<top;i++) { edge[S[i]].cap-=temp; edge[S[i]^1].cap+=temp; } res+=temp; top=inser; u=edge[S[top]]. from ; } if (u!=end&&gap[dep[u]-1]==0) //出现断层,无增广路 break ; for (i=cur[u];i!=-1;i=edge[i].next) if (edge[i].cap!=0&&dep[u]==dep[edge[i].to]+1) break ; if (i!=-1) { cur[u]=i; S[top++]=i; u=edge[i].to; } else { int min=n; for (i=head[u];i!=-1;i=edge[i].next) { if (edge[i].cap==0) continue ; if (min>dep[edge[i].to]) { min=dep[edge[i].to]; cur[u]=i; } } --gap[dep[u]]; dep[u]=min+1; ++gap[dep[u]]; if (u!=start)u=edge[S[--top]]. from ; } } return res; } |
给边赋值时,养成习惯用加法,防止有重边!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | //**************************************************** //最大流模板 //初始化:g[][],start,end //****************************************************** const int MAXN=110; const int INF=0x3fffffff; int g[MAXN][MAXN]; //存边的容量,没有边的初始化为0 int path[MAXN],flow[MAXN],start,end; int n; //点的个数,编号0-n.n包括了源点和汇点。 queue< int >q; int bfs() { int i,t; while (!q.empty())q.pop(); //把清空队列 memset(path,-1, sizeof (path)); //每次搜索前都把路径初始化成-1 path[start]=0; flow[start]=INF; //源点可以有无穷的流流进 q.push(start); while (!q.empty()) { t=q.front(); q.pop(); if (t==end) break ; //枚举所有的点,如果点的编号起始点有变化可以改这里 for (i=0;i<=n;i++) { if (i!=start&&path[i]==-1&&g[t][i]) { flow[i]=flow[t]<g[t][i]?flow[t]:g[t][i]; q.push(i); path[i]=t; } } } if (path[end]==-1) return -1; //即找不到汇点上去了。找不到增广路径了 return flow[end]; } int Edmonds_Karp() { int max_flow=0; int step,now,pre; while ((step=bfs())!=-1) { max_flow+=step; now=end; while (now!=start) { pre=path[now]; g[pre][now]-=step; g[now][pre]+=step; now=pre; } } return max_flow; } |
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· DeepSeek火爆全网,官网宕机?本地部署一个随便玩「LLM探索」
· 开发者新选择:用DeepSeek实现Cursor级智能编程的免费方案
· 【译】.NET 升级助手现在支持升级到集中式包管理
· 独立开发经验谈:如何通过 Docker 让潜在客户快速体验你的系统
· Tinyfox 发生重大改版