网络最大流(EK)
以前在oi中见到网络流的题都是直接跳过,由于本蒟蒻的理解能力太弱,导致网络流的学习不断推迟甚至被安排在了tarjan之后,原本计划于学习完最短路后就来学网络流的想法也随之破灭,在看完众多大佬
的博客后,我发现我不怎么能看懂(因为我自己太菜了),所以特来写一篇整理一下自己所学到的.
常见的网络流算法根据优化程度有FF<EK<Dinic<ISAP,由于后两种算法比较复杂,我至今也没有很好的理解,今天只要是我自己的一些对EK的理解.
首先需要了解一下什么是网络最大流:
网络流:所有弧上流量的集合f={f(u,v)},称为该容量网络的一个网络流.
-
定义:带权的有向图G=(V,E),满足以下条件,则称为网络流图(flow network):
- 仅有一个入度为0的顶点s,称s为源点
- 仅有一个出度为0的顶点t,称t为汇点
- 每条边的权值都为非负数,称为该边的容量,记作c(i,j)。
弧的流量:通过容量网络G中每条弧< u,v>,上的实际流量(简称流量),记为f(u,v);
网络流具有以下性质:
1.边所容纳的流量<=该边的最大容量.
2.反对称性,即f[u][v]=-f[v][u].
3.从源点流出的流量总是等于汇点汇聚的流量,对于其他的普通点来说,入度来的流量和等于出度出去的流量和.
最大网络流:网络流量图G中,最大的可行流称为网络最大流
我们定义:
源点:只有流出去的点
汇点:只有流进来的点
流量:一条边上流过的流量
容量:一条边上可供流过的最大流量
残量:一条边上的容量-流量
增广路:找到一条从源点到汇点的路径,使得路径上任意一条边的残量>0(注意是大于而不是大于等于,这意味着这条边还可以分配流量),这条路径便称为增广路
EK:不断找到一条起点到终点的路径,若有,找出这条路径上每一条边的最小值,然后将这条路上的每一条正向边减去这条路的流量,再在这条路上的每一条反向边加上这条边的流量,再在剩下的图上寻找新的路径,使总流量增加。然后形成新的图,再寻找新路径…..直到找不到从起点到终点的路径为止,最大流就算出来了。
#include<bits/stdc++.h> using namespace std; int st[1000010],vis[1000010],n,m,s,flow[1000010],maxflow,t,x,y,z,tot=-1,pre[1000010],q[1000010]; struct node { int from,to,val,last; /* from记录起点 to记录重点 val记录这条边的流量 last记录起点和当前这条边的起点一样的边的序号 */ }e[1000001]; int mn(int x,int y) { if(x>y) return y; return x; } void add(int f,int t,int v) {//链式前向星 tot++;//当前这条边的序号 e[tot].from=f; e[tot].to=t; e[tot].val=v; e[tot].last=st[f]; st[f]=tot; return ; } bool bfs(int s,int m)//寻找从起点到终点的路径 { int t=0,h=1; for(int i=1;i<=n;i++) { vis[i]=-1;//一开始把所有的点(包括起点和终点)标志为没有走过 pre[i]=-1;//记录上一条到达i点的边的起点的编号 } t++; q[t]=s;//将起点入队 vis[s]=1; flow[s]=2147483647; while(t>=h) { int u=q[h];//拿出队首 vis[u]=0;//当前的队首标志为不在队列里 h++;//踢出队首 for(int i=st[u];i!=0;i=e[i].last) { int v=e[i].to; if(e[i].val!=0 && vis[v]==-1)//如果当前遍历到的点不在队列里(避免重复入队)就入队 { flow[v]=mn(flow[u],e[i].val);//更新这条路径的流量(比较,求出最小值) pre[v]=i;//编号为i的这条边到达了v点,更新pre[v] t++;//入队 q[t]=v; //将 vis[v]=u; } } } if(vis[m]!=-1)//从当前点s可以走到点m return true; return false;//如果运行到了这里,说明再也找不到从起点到终点的路了 } void update(int s,int t)//一个回溯过程 {//如果能进这个函数,说明从当前点s可以走到点t int now=t;//从终点往回查找 while(now!=s)//如果当前回溯到的点不是起点 {//继续找 int i=pre[now]; e[i].val-=flow[t];//正向边加上这条边的流量 e[i^1].val+=flow[t];//建反向边,减去这条边的流量 now=e[i].from;//更新当前回溯到的点 } maxflow+=flow[t];//加上当前这条边的流量 } void EK(int s,int t) { maxflow=0;//重置最大流 while(bfs(s,t)==true)//如果当前找得到一条从起点到终点的路 update(s,t);//加上这条路的流量 } int main() { scanf("%d %d %d %d",&n,&m,&s,&t); for(int i=1;i<=m;i++) { scanf("%d %d %d",&x,&y,&z); add(x,y,z);//建正向边 add(y,x,0);//建反向边 } EK(s,t);//求最大流 printf("%d",maxflow);//输出 return 0; }