洛谷3376
给出一个网络图,以及其源点和汇点,求出其网络最大流。
EK算法:
在每条边都加了一条反向的,权值为0的边
反向边的作用:留下一个标记,让后面的流有机会让前面的流走另一条路。
直接从s到t广搜即可,从s开始不断向外广搜,通过权值大于0的边,直到找到t为止,(在过程中记录下每条边的编号),然后找到该路径上边权最小的边,记为mi,然后最大流加mi,然后把该路径上的每一条边的边权减去mi,每条的反向边都加上mi,直到找不到一条增广路为止。
怎么通过一条边的编号求它的反向边的编号???
由于正向边和反向边是一起加的,所以反向边的编号与正向边的编号只相差1。(异或1即可)
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
#define INF 0x7fffffff
struct Edge {
int to,next,val;
} edge[200010];
int head[10005],cnt = 0;
void add(int u,int v,int val)
{
edge[cnt].to = v;
edge[cnt].val = val;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int s,e;
int vis[10005],preid[10005],tag[10005];
//vis[i]表示在本次bfs中i是否被访问过
//preid[i]表示i的前一个节点编号
//tag[i]表示preid到i的边的下标编号
bool bfs()
{
memset(preid,0,sizeof(preid));
memset(tag,0,sizeof(tag));
memset(vis,0,sizeof(vis));
queue<int> q;
q.push(s); //把起点入队
vis[s] = 1;
while( !q.empty() )
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next) //遍历与x相邻的节点
{
Edge t = edge[i];
if( vis[t.to] ) continue;
if( t.val ) //如果这条边的权值大于0,表明可以走
{
vis[t.to] = 1;
preid[t.to] = x; //preid置为x
tag[t.to] = i; //tag置为i
if( t.to == e ) return true; //到终点说明成功找到一条路径
q.push(t.to);
}
}
}
return false;
}
int EK()
{
int ans = 0;
while( bfs() )
{
int mi = INF;
for (int t = e; t != s; t = preid[t])
{
mi = min(mi,edge[tag[t]].val); //回溯找到最小流量
}
for (int t = e; t != s; t = preid[t])
{
edge[tag[t]].val -= mi; //正边减去最小流量
edge[tag[t]^1].val += mi; //负边加上最小流量
}
ans += mi; //答案加上流量
}
return ans;
}
int main()
{
int n,m;
scanf("%d%d%d%d",&n,&m,&s,&e);
for (int i = 1; i <= n; i++)
{
head[i] = -1;
}
for (int i = 1; i <= m; i++)
{
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
add(x,y,v);
add(y,x,0);
}
printf("%d\n",EK());
return 0;
}
算法复杂度上界为O(VE)