以前在oi中见到网络流的题都是直接跳过,由于本蒟蒻的理解能力太弱,导致网络流的学习不断推迟甚至被安排在了tarjan之后,原本计划于学习完最短路后就来学网络流的想法也随之破灭,在看完众多大佬
的博客后,我发现我不怎么能看懂(因为我自己太菜了),所以特来写一篇整理一下自己所学到的.
常见的网络流算法根据优化程度有FF
首先需要了解一下什么是网络最大流:
网络流:所有弧上流量的集合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
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;
}