网络流算法总汇(ek,dinic,isap)

Posted clover_hxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流算法总汇(ek,dinic,isap)相关的知识,希望对你有一定的参考价值。

网络流算法之EK

最基础的网络流算法

不停地找增广路进行增广,直到无法增广为止

时间复杂度O(VE^2)

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int maxdata=0x7fffffff;
int capacity[200][200],c[1000][1000];//c[i][j]保存初值,因为每次计算都会改变capacity[i][j]的值,capacity[i][j]表示残留网络的容量
int flow[200];//标记从源点到当前点实际还有多少容量可用
int pre[200];//标记在这条路径上当前节点的前驱,同时标记该节点是否在队列中
int x[1000],y[10000],len[10000];//x,y,len存储每条边的信息
int n,m,p; //n 表示边数,m表示节点数,p表示操作数
queue<int> myqueue;
int bfs(int src,int des)

  int i,j;
  while(!myqueue.empty())//队列清空
   myqueue.pop();
  for (i=1;i<m+1;i++)
   pre[i]=-1;//pre[i]=-1表示该点没有入队
  pre[src]=0;
  flow[src]=maxdata;
  myqueue.push(src);
  while (!myqueue.empty())
   
    int index=myqueue.front();
    myqueue.pop();
    if (index==des)//index==des说明所有到终点的路已经都搜索完了
     break;
    for (i=1;i<m+1;i++)
     
      if (i!=src&&capacity[index][i]>0&&pre[i]==-1)//i不是源点,且从当前点可以到达i点,i点不在队列中,则i点入队
      
      pre[i]=index;//该路径上i的前驱点为index
      flow[i]=min(capacity[index][i],flow[index]);//要通过必须满足流量小于等于所有边的最小值
      myqueue.push(i);
      
     
   
   if (pre[des]==-1)
    return -1;
   else
    return flow[des];

int maxflow(int src,int des)//src表示源点,des表示汇点

   int increasement=0;
   int sumflow=0;
   while((increasement=bfs(src,des))!=-1)//有可以流到汇点的路径
    
      int k=des;
      while (k!=src)
       
        int last=pre[k];
        capacity[last][k]-=increasement;//用路径上每一条边的容量减去该路径的流量
        capacity[k][last]+=increasement;//给程序反悔和改正的机会
        k=last;
       
      sumflow+=increasement;
    
   return sumflow;

int main()

   int i,j,l;
   int start,end,ci;
   cin>>m>>n;
   memset(capacity,0,sizeof(capacity));
   memset(flow,0,sizeof(flow));
   for (i=1;i<=n;i++)
    
      cin>>start>>end>>ci;
      x[i]=start; y[i]=end; len[i]=ci;
      if (start==end)
        continue;
      c[start][end]=capacity[start][end]+=ci;
      c[end][start]=capacity[end][start]+=ci;
    
   scanf("%d",&p);
   for (l=1;l<=p;l++)
     
      int x1,y1,z1;
      for (i=1;i<=m;i++)
      for (j=1;j<=m;j++)
       capacity[i][j]=c[i][j];
      scanf("%d%d%d",&x1,&y1,&z1);
      if (x1==0)
      printf("%d\\n",maxflow(y1,z1));
      if (x1==1)//改变编号Y1的边的容量,
      
      c[x[y1]][y[y1]]=capacity[x[y1]][y[y1]]=z1;
      c[y[y1]][x[y1]]=capacity[y[y1]][x[y1]]=z1;
      
     
//如果把网络流的图看作一系列的管道,那么求最大流就相当于从源点处注水,求最多最终有多少水可以流到汇点 



网络流算法之dinic(适用于二分图,二分图中O(SQRT(V)E))

O(V^2E)

Dinic算法算是Ek算法的进阶版,在寻找增广路的基础上,增加了分层图的概念。

• 什么是分层图?

• 顾名思义,根据从S到每个点的远近不同,将�图分成若干层。

• Dinic算法即在寻找增广路的基础上,要求增广路上的每个点,属于分层图上不同的层。

多路增广
• 每次不是寻找一条增广路,而是在DFS中,只要可以就递归增广下去,实际上形成了一张增广网。
当前弧优化
• 对于每一个点,都记录上一次检查到哪一条边。因为我们每次增广一定是彻底增广(即这条已经被增广过的边已经发挥出了它全部的潜力,不可能再被增广了),下一次就不必再检查它,而直接看第一个未被检查的边


// Codevs 1993

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
using namespace std;

const int max_n = 220;
const int max_m = 220;
const int max_e = max_m * 2;
const int inf = 1e9;

int point[max_n], nxt[max_e], v[max_e], remain[max_e], tot;
int cur[max_n], deep[max_n], n, m;//deep记录分层,cur记录检查到哪一条边(当前弧优化) 

inline int getnum() 
	char c; int ans = 0;
	while ((c = getchar()) == ' ' || c == '\\n' || c == '\\r');
	ans = c - '0';
	while (isdigit(c = getchar())) ans = ans * 10 + c - '0';
	return ans;


inline void addedge(int x, int y, int cap) 
	tot++; nxt[tot] = point[x]; point[x] = tot; v[tot] = y; remain[tot] = cap;
	tot++; nxt[tot] = point[y]; point[y] = tot; v[tot] = x; remain[tot] = 0;


inline bool bfs(int s, int t) //排除容量已经为0的边,进行分层 

	memset(deep, 0x7f, sizeof(deep));
	for (int i = 1; i <= n; i++)
		cur[i] = point[i];
	deep[s] = 0;
	queue<int> q;
	q.push(s);

	while (!q.empty()) 
		int now = q.front(); q.pop();
		for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp])//注意tmp!=-1,因为从0开始存储 
			if (deep[v[tmp]] > inf && remain[tmp])
				deep[v[tmp]] = deep[now] + 1, q.push(v[tmp]);
	
	return deep[t] < inf;


int dfs(int now, int t, int limit) //寻找增广路,实际上应该是一个增广网,limit表示当前路径的源点到现在最窄的(剩余流量最小)的边的剩余流量 

	if (!limit || now == t) return limit;
	int flow = 0, f;
	for (int tmp = cur[now]; tmp != -1; tmp = nxt[tmp]) 
		cur[now] = tmp;
		if (deep[v[tmp]] == deep[now] + 1 && (f = dfs(v[tmp], t, min(limit, remain[tmp])))) 
			flow += f;
			limit -= f;
			remain[tmp] -= f;
			remain[tmp ^ 1] += f;
			if (!limit) break;
		
	
	return flow;


inline int dinic(int s, int t) 
	int ans = 0;
	while (bfs(s, t))
		ans += dfs(s, t, inf);
	return ans;


int main() 
	tot = -1;	
	memset(point, -1, sizeof(point));
	memset(nxt, -1, sizeof(nxt));

	m = getnum(); n = getnum();
	for (int i = 1; i <= m; i++) 
		int x = getnum(); 
		int y = getnum();
		int cap = getnum();
		addedge(x, y, cap);
	

	cout << dinic(1, n) << endl;



网络流算法之isap

ISAP( Improved Shortest Augmenting Path)也是基于分层思想的最大流算法。所不同的是,它省去了Dinic每次增广后需要重新构建分层图的麻烦,而是在每次增广完成后自动更新每个点的『 标号』 (也就是所在的层)

时间复杂度为O(V^2E),和dinic相同,但是实践证明,在一般的网络流图中, ISAP似乎更具时间上的优势。

// Codevs 1993

#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
using namespace std;

const int max_n = 220;
const int max_m = 220;
const int max_e = max_m * 2;
const int inf = 1e9;

int point[max_n], nxt[max_e], v[max_e], remain[max_e], tot;
int deep[max_n], num[max_n], cur[max_n], lastedge[max_n];//deep记录分层信息,num记录每一层的点数,lastedge记录前驱节点 
bool vis[max_n];
int n, m;

inline int getnum() //快速读入 

	char c; int ans = 0;
	while ((c = getchar()) == ' ' || c == '\\n' || c == '\\r');
	ans = c - '0';
	while (isdigit(c = getchar())) ans = ans * 10 + c - '0';
	return ans;


inline void addedge(int x, int y, int cap) //处理边的信息,注意从0开始存 

	tot++; nxt[tot] = point[x]; point[x] = tot; v[tot] = y; remain[tot] = cap;
	tot++; nxt[tot] = point[y]; point[y] = tot; v[tot] = x; remain[tot] = 0;


inline int addflow(int s, int t) 

	int ans = inf, now = t;
	while (now != s) 
		ans = min(ans, remain[lastedge[now]]);
		now = v[lastedge[now] ^ 1];
	
	now = t;
	while (now != s) 
		remain[lastedge[now]] -= ans;
		remain[lastedge[now] ^ 1] += ans;
		now = v[lastedge[now] ^ 1];//因为isap是从汇点扩展的 
	
	return ans;


inline void bfs(int t) 
	for (int i = 1; i <= n; i++)
		deep[i] = n;
	memset(vis, 0, sizeof(vis));
	queue<int> q;
	deep[t] = 0; 
	q.push(t); vis[t] = true;
	while (!q.empty()) 
		int now = q.front(); q.pop();
		for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp])
			if (!vis[v[tmp]] && remain[tmp ^ 1]) //remain[tmp^1]求逆向边 
			
				vis[v[tmp]] = true;
				deep[v[tmp]] = deep[now] + 1;
				q.push(v[tmp]);
			
	


inline int isap(int s, int t) 
	int ans = 0, now = s;
	bfs(t);
	for (int i = 1; i <= n; i++) num[deep[i]]++;
	for (int i = 1; i <= n; i++) cur[i] = point[i];//cur 当前弧优化,记录当前检查到哪一条边 

	while (deep[s] < n) 
		if (now == t) 
			ans += addflow(s, t);
			now = s;
		

		bool has_find = false;
		for (int tmp = cur[now]; tmp != -1; tmp = nxt[tmp]) 
			int u = v[tmp];
			if (deep[u] + 1 == deep[now] && remain[tmp]) 
				has_find = true;
				cur[now] = tmp;
				lastedge[u] = tmp;
				now = u;
				break;
			
		

		if (!has_find) //没有找到增广路,对now当前点重新标号 
			int minn = n - 1;
			for (int tmp = point[now]; tmp != -1; tmp = nxt[tmp])
				if (remain[tmp])
					minn = min(minn, deep[v[tmp]]); //从所以相连节点中选取编号最小的 
			if (!(--num[deep[now]])) break;//GAP优化,如果编号出现了断层,说明不能再增广,那么此时的答案即为最大流 
			num[deep[now] = minn + 1]++;
			cur[now] = point[now];
			if (now != s)
				now = v[lastedge[now] ^ 1];
		
	
	return ans;


int main() 
	tot = -1;	
	memset(point, -1, sizeof(point));
	memset(nxt, -1, sizeof(nxt));

	m = getnum(); n = getnum();
	for (int i = 1; i <= m; i++) 
		int x = getnum(); 
		int y = getnum();
		int cap = getnum();
		addedge(x, y, cap);
	

	cout << isap(1, n) << endl;



以上是关于网络流算法总汇(ek,dinic,isap)的主要内容,如果未能解决你的问题,请参考以下文章

网络流小结

网络流Dinic(本篇介绍最大流)

ISAP网络流算法

图论算法-网络最大流EK;Dinic

8/26 网络流Dinic算法+最小割+cf

ISAP算法对 Dinic算法的改进