网络流(1)

网络流(1)

基本概念和性质

原文见我的博客

最小割

设容量网络 N = &lt; V , E , c , s , t &gt; , A ⊂ V 且 s ∈ A , t ∈ V − A , 称 ( A , V − A ) = { &lt; i , j &gt; ∣ &lt; i , j &gt; ∈ E 且 i ∈ A , j ∈ V − A } N = &lt;V,E,c,s,t&gt;,A \subset V 且 s ∈ A,t ∈ V-A ,称(A,V-A) = \{ &lt;i,j&gt; | &lt;i,j&gt; ∈ E 且 i ∈ A , j ∈ V-A \} N=<V,E,c,s,t>,AVsA,tVA,(A,VA)={<i,j><i,j>EiA,jVA}为N的割集, c ( A , V − A ) = ∑ &lt; i , h &gt; ∈ ( A , A ˉ ) c ( i , j ) c(A,V-A) = \sum_{ &lt;i,h&gt; ∈ (A,\bar{A}) } c(i,j) c(A,VA)=<i,h>(A,Aˉ)c(i,j) 为割集 c ( A , V − A ) ​ c(A,V-A)​ c(A,VA)容量,容量最小的割集称为最小割集

引理1

设容量网络 N = &lt; V , E , c , s , t &gt; N = &lt;V,E,c,s,t&gt; N=<V,E,c,s,t>,f为N上的任一可行流, A ⊂ V 且 s ∈ A , t ∈ V − A , 则 v ( f ) = ∑ &lt; i , j &gt; ∈ ( A , A ˉ ) f ( i , j ) − ∑ &lt; j , i &gt; ∈ ( A , A ˉ ) f ( j , i ) A \subset V 且 s ∈ A , t ∈ V-A,则 v(f) = \sum_{&lt;i,j&gt; ∈ (A,\bar{A})} f(i,j) - \sum_{ &lt;j,i&gt;∈ (A,\bar{A})} f(j,i) AVsA,tVA,v(f)=<i,j>(A,Aˉ)f(i,j)<j,i>(A,Aˉ)f(j,i)

引理2

设f是任一可行流,(A,V-A)是任一割集,则 v ( f ) ≤ c ( A , V − A ) v(f) \leq c(A,V-A) v(f)c(A,VA)

引理3

设f是一个可行流,(A,V-A)是一个割集。如果 v ( f ) = c ( A , V − A ) v(f) = c(A,V-A) v(f)=c(A,VA),则f是最大流,(A,V-A)为最小割集

增广链

设容量网络 N = &lt; V , E , c , s , f &gt; N = &lt;V,E,c,s,f&gt; N=<V,E,c,s,f>,f为N上的一个可行流

  1. N中流量等于容量的边称为饱和边,否则称为非饱和边
  2. 流量等于0的边称为零流边,否则为非零流边
  3. 不考虑边的方向,N中从顶点i到顶点j的一条边不重复的路径称做i-j链,链的方向为从i到j。链中与链的方向一致的边称作前向边,否则称为后向边
  4. 如果链中所有前向边都是非饱和的,所有后向边都是非零流的,则称这条链为i-j增广链

最大流的性质

定理1

可行流f为最大流 等价于 不存在关于f的s-t增广链

证明:1555296858689

1555296884115

定理2(最大流最小割集定理)

容量网络的最大流的流量等于最小割集的容量。

Fold-Fulkeson算法

思想

从给定初始可行流(通常取零流开始),取当前可行流的s-t增广链P,修改P上流量得到新的可行流,重复进行,直到不存在s-t增广链为止

形象的理解

伪代码

顶点j得到标号表示已经找到从s到j的增广链,标号为 ( l j , δ j ) (l_j,\delta_j) (lj,δj) ,其中 δ j \delta_j δj等于直到j为止链上所有前向边的容量和流量之差以及所有后向边的流量的最小值,$l_j = +i 或 或 l_j = -i , 前 者 表 示 链 是 从 i 到 j 且 ,前者表示链是从i到j且 ,ij<i,j> 是 前 向 边 , 后 者 表 示 链 是 从 i 到 j 且 是前向边,后者表示链是从i到j且 ij<i,j>$是后向边。

时间复杂度

假设所有容量都是正整数,则算法时间复杂度为 O ( m C ) O(mC) O(mC),m为边数, C = ∑ &lt; s , j &gt; ∈ E c ( s , j ) ​ C = \sum_{&lt;s,j&gt;∈ E}c(s,j)​ C=<s,j>Ec(s,j) 。(每次流量至少加1,至多C个阶段),而每阶段标号和修改增广链流量都需要O(m)

改进方法

  1. 每次求最短的s-t增广链,即用bfs代替dfs找增广链,Edmonds-Karp算法 O ( n m 2 ) O(nm^2) O(nm2)

    以POJ1273题为例,链接http://poj.org/problem?id=1273

    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<cmath>
    
    using namespace std;
    int N,M ;
    int G[300][300] = {} ;
    int Prev[300] = {} ;
    bool vis[300] = {} ;
    unsigned Augument()
    {
    	int v ;
    	queue<int> q ;
    	memset(Prev,0,sizeof(Prev)) ;
    	memset(vis,0,sizeof(vis)) ;
    	Prev[1] = 0 ;
    	vis[1] = 1 ;
    	q.push(1) ;
    	bool found = 0 ;
    	while ( !q.empty() ) {
    		v = q.front() ;
    		q.pop() ;
    		for (int i = 1 ; i <= M ; ++i ) {
    			if ( G[v][i] > 0 && vis[i] == 0 ) {
    				Prev[i] = v ;
    				vis[i] = 1 ;
    				if ( i == M ) {
    					found = 1 ;
    					while( !q.empty() ) q.pop() ;
    					break ;
    				}
    				else {
    					q.push(i) ;
    				}
    			}
    		}
    	}
    	if ( !found ) return 0 ;
    	int nMinFlow = 1 << 30 ;
    	v = M ;
    	while( Prev[v] ) {
    		nMinFlow = min(nMinFlow,G[Prev[v]][v]) ;
    		v = Prev[v] ;
    	}
    	v = M ;
    	while( Prev[v] ) {
    		G[Prev[v]][v] -= nMinFlow ;
    		G[v][Prev[v]] += nMinFlow ;
    		v = Prev[v] ;
    	
    	}
    	
    	return nMinFlow ;
    }
    int main () {
    	while( cin >> N >> M ) {
    		int s,e,c ;
    		memset(G,0,sizeof(G)) ;
    		for ( int i = 0 ; i < N ; ++i ) {
    			cin >> s >> e >> c ;
    			G[s][e] += c ;
    		}
    		unsigned ans = 0 ,aug = 0 ;
    		while( aug = Augument() ) {
    			ans += aug ;
    		}
    		cout << ans << endl ;
    	}
    	return 0;
    } 
    
  2. 一次标号修改尽可能多条s-t增广链上的流量——Dinic算法

Dinic算法

辅助网络(残余网络)

证明正确性的基础——引理4和引理5(详细证明过于繁琐,略去)

引理4

引理5

算法实现

C++代码

C++代码(残余网络版,模板题链接http://bailian.openjudge.cn/practice/1469/)

#include <iostream>
#include <queue>
#include<bits/stdc++.h>
using namespace std;
int G[1000][1000];
int Prev[1000]; //路径上每个节点的前驱节点
bool Visited[1000];
int Layer[1000];   //Layer[i]是节点i的层号  
int n,m,p;  //m是顶点数目,顶点编号从1开始 1是源,m是汇, n是边数

bool CountLayer()   { //分层
	int layer = 0;     
	deque<int>q;
	memset(Layer,0xff,sizeof(Layer)); //都初始化成-1
	Layer[1] = 0;  q.push_back(1);
	while( ! q.empty()) {
		int v = q.front();
		q.pop_front();
		for( int j = 1; j <= m; j ++ ) {
			if( G[v][j] > 0 && Layer[j] == -1 ) {
			//Layer[j] == -1 说明j还没有访问过
				Layer[j] = Layer[v] + 1; 
				if( j == m ) return true;  //分层到汇点即可
				else q.push_back(j);
			}
		}
	}
	return false;
}
int Dinic() {
	int i;  	
	int s;
	int nMaxFlow = 0;
	deque<int> q; //DFS用的栈
	while( CountLayer() ) { //只要能分层
		q.push_back(1);	//源点入栈	
		memset(Visited,0,sizeof(Visited));   
		Visited[1] = 1;
		while( !q.empty()) {
			int nd = q.back();
			if( nd == m ) { // nd是汇点
				//在栈中找容量最小边
				int nMinC = 1 << 20;
				int nMinC_vs; //容量最小边的起点
				for( i = 1;i < q.size(); i ++ ) {
					int vs = q[i-1];
					int ve = q[i];
					if( G[vs][ve] > 0 ) {
						if( nMinC > G[vs][ve] ) {
							nMinC = G[vs][ve];
							nMinC_vs = vs;
						}
					}
				}
				//增广,改图
				nMaxFlow += nMinC;
				for( i = 1;i < q.size(); i ++ ) {
					int vs = q[i-1];
					int ve = q[i];
					G[vs][ve] -= nMinC; //修改边容量 
					G[ve][vs] += nMinC; //添加反向边
				}
				//退栈到 nMinC_vs成为栈顶,以便继续dfs
				while( !q.empty() && q.back() != nMinC_vs ) { 
					Visited[q.back()] = 0; 
					q.pop_back();
				}

			}
			else { //nd不是汇点
				for( i = 1;i <= m; i ++ )  {
				      if( G[nd][i] > 0 && Layer[i] == Layer[nd] + 1 &&
				   	! Visited[i]) {
						//只往下一层的没有走过的节点走
						Visited[i] = 1;
						q.push_back(i);
						break;
					}
				}
				if( i > m)  //找不到下一个点
					q.pop_back(); //回溯
			}
		}
	}
	return nMaxFlow;
}

int main()
{
	int T ;
	scanf("%d",&T) ;
	while (T-- ) {
		//m是顶点数目,顶点编号从1开始
		scanf("%d %d",&p,&n) ;
		int i,j,k;
		int s,e,c;
		memset( G,0,sizeof(G)) ;
		for( i = 1;i <= p;i ++ ) {
			scanf("%d",&c) ;
			for ( j = 1 ; j <= c ; ++j ) {
				scanf("%d",&e) ;
				G[e+1][i+n+1] = 1 ;
		
			}
		}
		for(int i = 1 ; i <= n ; ++i ) {
			G[1][i+1] = 1 ; 
	
		} 
		for(int i = 1 ; i <= p ; ++i ) {
			G[i+n+1][p+n+2] = 1 ;
	
		}
		m = p+n+2 ;
		unsigned int MaxFlow = Dinic();
		if(MaxFlow == p ) printf("YES\n") ;
		else printf("NO\n") ;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值