网络流汇总

11 篇文章 0 订阅

网络流

网络流(network-flows)是一种类比水流的解决问题方法,与线性规划密切相关。网络流的理论和应用在不断发展,出现了具有增益的流、多终端流、多商品流以及网络流的分解与合成等新课题。网络流的应用已遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等众多领域。

定义
有向图 G = ( V , E ) G = (V, E) G=(V,E)中:

  • 有唯一的一个源点S(入度为0:出发点)
  • 有唯一的一个汇点T(出度为0:结束点)
  • 图中每条弧(u, v)都有一非负容量c(u, v)

满足上述条件的图G称为网络流图。记为: G = ( u , v , c ) G = (u, v, c) G=u,v,c)
可行流
每条弧 ( u , v ) (u, v) (u,v)上给定一个实数 f ( u , v ) f(u, v) f(u,v),满足:有 0 ≤ f ( u , v ) ≤ c ( u , v ) 0\leq f(u, v)\leq c(u, v) 0f(u,v)c(u,v),则 f ( u , v ) f(u, v) f(u,v)称为弧 ( u , v ) (u, v) (u,v)上的流量。
如果有一组流量满足条件:

  • 源点s:流出量=整个网络的流量
  • 汇点t:流入量=整个网络的流量
  • 中间点:总流入量=总流出量

那么整个网络中的流量成为一个可行流。

网络流的三个基本性质

  • 容量限制:对任意 u , v ∈ V u, v ∈ V u,vV f ( u , v ) ≤ c ( u , v ) f(u, v) \leq c(u, v) f(u,v)c(u,v)
  • 反对称性:对任意 u , v ∈ V u, v ∈ V u,vV f ( u , v ) = − f ( v , u ) f(u, v) = -f(v, u) f(u,v)=f(v,u)。从u到v的流量一定是从v到u的流量的相反值。
  • 流守恒性:对任意u,若u不为S或T,一定有 ∑ f ( u , v ) = 0 , ( u , v ) ∈ E ∑f(u,v)=0,(u,v)∈E f(u,v)=0(u,v)E。即u到相邻节点的流量之和为0,因为流入u的流量和u点流出的流量相等,u点本身不会"制造"和"消耗"流量。

容量网络&流量网络&残留网络

  • 容量网络就是关于容量的网络 基本是不改变的(极少数问题需要变动)
  • 流量网络就是关于流量的网络 在求解问题的过程中
    通常在不断的改变 但是总是满足上述三个性质
    调整到最后就是最大流网络 同时也可以得到最大流值
  • 残留网络往往概括了容量网络和流量网络 是最为常用的
    残留网络=容量网络-流量网络
    这个等式是始终成立的 残留值当流量值为负时甚至会大于容量值
    流量值为什么会为负?有正必有负,记住斜对称性!

割&割集

  • 割:E是弧的集合,设E·为E的一个子集,如果G在删除 E·之后不再连通,则E~为G的割。
  • 割集:容量网络 G = ( V , E , C ) G = (V, E, C) G=(V,E,C),Vs,Vt为源、汇点,若有边集E·为E的子集,将G为两个子图G1,G2,即点集V被部分其为两个顶点集合分别S,~S,必有S∪ ~S = V,S∩ ~S = Ø,Vs ∈ S, Vt ∈ ~S。若有边集E·为E的子集,且满足下列两个性质,则称E·为G的割集),记为E· = (S, ~S)。
    割集(S, ~S)中所有始点在S,终点在 ~S的边的容量之和,成为(S, ~S)的割集容量,记为C(S, ~S)。容量网络G的割集有很多个,其中割集容量最小这成为网络G的最小割集容量(简称最小割集)。
    性质:
    1. 若把整个截集的弧从网络 G = ( V , E , C ) G=(V,E,C) G=(V,E,C)中丢去,则不存在从vs和vt的有向路,即图(V,E-E·)不连通。
    2. 只要没把整个截集删去,就存在从vs和vt的有向路,即当E’‘为E的真子集,图G(V,E-E··)仍连通。
      由此可知,截集是从起点vs到终点vt的必经之路。

网络流与最大流

给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇点T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow)。
The network flow problem considers a graph G with a set of sources S and sinks T and for which each edge has an assigned capacity (weight), and then asks to find the maximum flow that can be routed from S to T while respecting the given edge capacities.

通俗的讲,就是由若干个运货点,一个是起点,一个是终点,有一些运货点由路相连,每条路有容量限制,走过那条路时运送的货物不能超过其中的容量限制,求最大流就是求从起点运送尽量多的货物到终点,到达终点的货物个数。
例如:
在这里插入图片描述
该图的最大流为4。

ans = f(v2, T) + f(v4, T)
	= min(f(v1, v2), c(v2, T)) + min(f(v3, v4), c(v4, T))
	= min(min(f(S, v1), c(v1, v2)), 3) + min(min(f(S, v3), c(v3, v4)), 1)
	= min(min(min(INF, c(S, v1)), 7), 3) + min(min(min(INF, c(S, v3)), 9), 1)
	= 3+1
	= 4

那么如何求最大流呢?可以采用著名的Dinic算法。

最大流算法(Dinic)
Dinic算法的基本思路:
  • 根据残量网络计算层次图。
  • 在层次图中使用DFS进行增广直到不存在增广路
  • 重复以上步骤直到无法增广
时间复杂度

因为在Dinic的执行过程中,每次重新分层,汇点所在的层次是严格递增的,而n个点的层次图最多有n层,所以最多重新分层n次。在同一个层次图中,因为每条增广路都有一个瓶颈,而两次增广的瓶颈不可能相同,所以增广路最多m条。搜索每一条增广路时,前进和回溯都最多n次,所以这两者造成的时间复杂度是O(nm);而沿着同一条边(i,j)不可能枚举两次,因为第一次枚举时要么这条边的容量已经用尽,要么点j到汇不存在通路从而可将其从这一层次图中删除。综上所述,Dinic算法时间复杂度的理论上界是O(n^2*m)。

小贴士:

一般情况下在Dinic算法中,我们只记录某一边的剩余流量.

  • 残量网络:包含反向弧的有向图,Dinic要循环的,每次修改过的图都是残量网络,
  • 层次图:分层图,以[从原点到某点的最短距离]分层的图,距离相等的为一层,(比如上图的分层为{s},{1,3},{2, 4},{t})
  • 增广 :在现有流量基础上发现新的路径,扩大发现的最大流量(注意:增加量不一定是这条路径的流量,而是新的流量与上次流量之差)
  • 增广路:在现有流量基础上发现的新路径.
  • 剩余流量:当一条边被增广之后(即它是增广路的一部分,或者说增广路通过这条边),这条边还能通过的流量.
  • 反向弧:我们在Dinic算法中,对于一条有向边,我们需要建立另一条反向边(弧),当正向(输入数据)边剩余流量减少I时,反向弧剩余流量增加I
    • 为什么需要反向弧?
      我们知道,当我们在寻找增广路的时候,在前面找出的不一定是最优解,如果我们在减去残量网络中正向边的同时将相对应的反向边加上对应的值,我们就相当于可以反悔从这条边流过。

(HDU1532)

问题描述
每当农场约翰的田地下雨时,一个池塘形成了贝西最喜欢的三叶草补丁。这意味着三叶草被水覆盖了一段时间并且需要相当长的时间才能再生。因此,Farmer John建造了一套排水沟,以便Bessie的三叶草补丁永远不会被水覆盖。相反,水被排放到附近的溪流中。作为一名王牌工程师,Farmer John还在每个沟渠开始时安装了调节器,因此他可以控制水流入该沟渠的速度。
农夫约翰不仅知道每个沟渠每分钟可以运输多少加仑的水,而且还知道沟渠的确切布局,这些沟渠从池塘中流出并相互进入并在潜在的复杂网络中流动。
根据所有这些信息,确定水可以从池塘运输到水流中的最大速率。对于任何给定的沟渠,水只沿一个方向流动,但可能有一种方式,水可以流动。
输入
输入包括几种情况。对于每种情况,第一行包含两个空格分隔的整数,N(0 <= N <= 200)和M(2 <= M <= 200)。N是Farmer John挖的沟渠数量。M是那些沟渠的交叉点数。交叉口1是池塘。交点M是流。以下N行中的每一行包含三个整数,Si,Ei和Ci。Si和Ei(1 <= Si,Ei <= M)表示该沟流动的交叉点。水将从Si流到Ei。Ci(0 <= Ci <= 10,000,000)是水流过沟渠的最大速率。
产量
对于每种情况,输出一个整数,即水可以从池塘中排空的最大速率。
样本输入
5 4
1 2 40
1 4 20
2 4 20
2 3 30
3 4 10
样本输出
50

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int maxn = 1e4;
const int INF = 1e9+7;
int n, m;
struct edge{
	int v, w, next;
}Edge[maxn];
int head[maxn], cnt = 0, dis[maxn];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w){
	Edge[cnt].v = v;
	Edge[cnt].w = w;
	Edge[cnt].next = head[u];
	head[u] = cnt++;
}
void add(int u, int v, int w){
	addEdge(u, v, w);
	addEdge(v, u, 0);
}
bool BFS(){
	memset(dis, -1, sizeof(dis));
	queue<int> q;
	q.push(1);
	dis[1] = 0;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = head[u]; i != -1; i = Edge[i].next){
			int v = Edge[i].v;
			if(Edge[i].w && dis[v]==-1){
				q.push(v);
				dis[v] = dis[u]+1;
			}
		}
	}
	return dis[m]!=-1;
}
int DFS(int u, int flow){
	int maxFlow = 0;
	if(u == m) return flow;
	for(int i = head[u]; i != -1; i = Edge[i].next){
		int v = Edge[i].v, w;
		if(dis[v] == dis[u]+1 && Edge[i].w && flow && (w = DFS(v, min(flow, Edge[i].w)))){
			flow-=w;
			maxFlow+=w;
			Edge[i].w-=w;
			Edge[i^1].w+=w;
		}
	}
	return maxFlow;
}
void dinic(){
	int maxFlow = 0;
	while(BFS()){
		maxFlow += DFS(1, INF);
	}
	printf("%d\n", maxFlow);
}
int main(){
	while(scanf("%d%d", &n, &m) != EOF){
		init();
		for(int i = 1; i <= n; i++){
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w);
		}
		dinic();
	}
	return 0;
} 

网络流-最小费用最大流

最小费用最大流问题是经济学和管理学中的一类典型问题。在一个网络中每段路径都有“容量”和“费用”两个限制的条件下,此类问题的研究试图寻找出:流量从A到B,如何选择路径、分配经过路径的流量,可以在流量最大的前提下,达到所用的费用最小的要求。如n辆卡车要运送物品,从A地到B地。由于每条路段都有不同的路费要缴纳,每条路能容纳的车的数量有限制,最小费用最大流问题指如何分配卡车的出发路径可以达到费用最低,物品又能全部送到。

定义

给定网络 G = ( V , E , C ) G = (V, E, C) G=(V,E,C) 每一弧 ( v i , v j ) (v_i,v_j) (vi,vj)上,除了已给容量 c i j c_{ij} cij外,还给了一个单位流量的费用 w ( v i , v j ) ≥ 0 w(v_i,v_j)\geq0 w(vi,vj)0(简记为 b i j b_{ij} bij)。所谓最小费用最大流问题就是要求一个最大流 f f f,使流的总输送费用最小,即求 f ∗ f^* f,使 w ( f ∗ ) = m i n ∑ ( v i , v j ) ∈ A b i j f i j w(f^*) =min\sum_{(v_i,v_j)\in A}b_{ij}f_{ij} w(f)=min(vi,vj)Abijfij

费用流算法(SPFA版本)
算法的基本思路:
  • 通过SPFA找一条 v s → v t v_s\rightarrow v_t vsvt的最短路。“距离”使用该路径上的边的单位费用之和来衡量。
  • 找出这条路径上的边的容量的最小值flow,则当前最大流maxFlow扩充flow,同时当前最小费用minCost扩充 f*minDis(s,t)。
  • 将这条路径上的每条正向边的容量都减少flow,每条反向边的容量都增加flow。
  • 重复以上步骤直到无法找到从源点到达汇点的路径。

(P3381洛谷)

题目描述
如题,给出一个网络图,以及其源点和汇点,每条边已知其最大流量和单位流量费用,求出其网络最大流和在最大流情况下的最小费用。
输入格式:
第一行包含四个正整数N、M、S、T,分别表示点的个数、有向边的个数、源点序号、汇点序号。
接下来M行每行包含四个正整数ui、vi、wi、fi,表示第i条有向边从ui出发,到达vi,边权为wi(即该边最大流量为wi),单位流量的费用为fi。
输出格式:
一行,包含两个整数,依次为最大流量和在最大流量情况下的最小费用。
input
4 5 4 3
4 2 30 2
4 3 20 3
2 3 20 1
2 1 30 9
1 3 40 5
output
50 280

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
    int t=1,num=0;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')t=-1;c=getchar();}
    while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();}
    return num*t;
}
const int maxn = 200010;
const int eMaxn = 5015;
const int INF = 0x3f3f3f3f;
int N, M, S, T;
struct Edge{
    int v, w, f, next;
}edge[maxn];
int head[eMaxn], cnt;
int dis[eMaxn], path[eMaxn];
bool vis[eMaxn];
void init(){
	N+=10;
    cnt = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w, int f){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].f = f;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
void add(int u, int v, int w, int f){
    addEdge(u, v, w, f);
    addEdge(v, u, 0, -f);
}
bool SPFA(){
    fill(dis, dis+N, INF);
    memset(vis, false, sizeof(vis));
    memset(path, -1, sizeof(path));
    queue<int> q;
    q.push(S);
    vis[S] = true;
    dis[S] = 0;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i!=-1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+edge[i].f){
                path[v] = i; 
                dis[v] = dis[u]+edge[i].f;
                if(!vis[v]){
                    q.push(v), vis[v] = true;
                }
            }
        }
    }
    return path[T]!=-1;
}
void MCMF(){
    int maxFlow = 0, minCost = 0;
    while(SPFA()){
        int MIN = INF;
        for(int u = path[T]; u!=-1; u = path[edge[u^1].v]){
            if(MIN > edge[u].w) MIN = edge[u].w;
        }
        for(int u = path[T]; u!=-1; u = path[edge[u^1].v]){
            minCost+=MIN*edge[u].f;
            edge[u].w-=MIN;
            edge[u^1].w+=MIN;
        }
        maxFlow += MIN;
    }
    printf("%d %d\n", maxFlow, minCost);
}
int main(){
    
    N = read(), M = read(), S = read(), T = read();
    init();
	for(int i = 0; i < M; i++){
        int u, v, w, f;
        u = read(), v = read(), w = read(), f = read();
        add(u, v, w, f);
    }
    MCMF();
    return 0;
} 

网络流-最大流最小割

最大流最小割定理是网络流理论的重要定理。是指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。

相关定理

定理一:
  如果 f f f是网络中的一个流, C U T ( S , T ) CUT(S,T) CUT(S,T)是任意一个割,那么 f f f的值等于正向割边的流量与负向割边的流量之差。
证明:
  设 X X X Y Y Y是网络中的两个顶点集合,用 f ( X , Y ) f(X,Y) f(X,Y)表示从 X X X中的一个顶点指向 Y Y Y的一个顶点的所有弧(弧尾在X中,弧头在Y中: X → Y X \rightarrow Y XY )的流量和。只需证明: f = f ( S , T ) − f ( T , S ) f=f(S,T)-f(T,S) f=f(S,T)f(T,S)即可。
下列结论成立:
如果X∩Y= ,那么: f ( X , ( Y 1 ∪ Y 2 ) ) = f ( X , Y 1 ) + f ( X , Y 2 ) f(X,(Y1∪Y2))=f(X,Y1)+f(X,Y2) f(X,(Y1Y2))=f(X,Y1)+f(X,Y2) f ( ( X 1 ∪ X 2 ) , Y ) = f ( X 1 , Y ) + f ( X 2 , Y ) f((X1∪X2),Y)=f(X1,Y)+f(X2,Y) f((X1X2),Y)=f(X1,Y)+f(X2,Y)成立。
  根据网络流的特点:
如果V既不是源点也不是汇点,那么: f ( V , S ∪ T ) − f ( S ∪ T , V ) = 0 f({V},S∪T)-f(S∪T,{V})=0 f(V,ST)f(ST,V)=0;任何一个点,流入的与流出的量相等。
如果V是源,那么: f ( V , S ∪ T ) − f ( S ∪ T , V ) = f f({V},S∪T)-f(S∪T,{V})=f f(V,ST)f(ST,V)=f
对于S中的所有点V都有上述关系式,相加得到: f ( S , S ∪ T ) − f ( S ∪ T , S ) = f f(S,S∪T)-f(S∪T,S)=f f(S,ST)f(ST,S)=f
又因为: f ( S , S ∪ T ) − f ( S ∪ T , S ) = ( f ( S , S ) + f ( S , T ) ) − ( f ( S , S ) + f ( T , S ) ) = f ( S , T ) − f ( T , S ) f(S,S∪T)-f (S∪T,S)= (f(S,S)+f (S,T))-(f(S,S) +f (T,S))= f(S,T)- f(T,S) f(S,ST)f(ST,S)=(f(S,S)+f(S,T))(f(S,S)+f(T,S))=f(S,T)f(T,S)
所以: f = f ( S , T ) − f ( T , S ) f= f(S,T)- f(T,S) f=f(S,T)f(T,S) 定理得证 。
推论一:
  如果f是网络中的一个流, C U T ( S , T ) CUT(S,T) CUT(S,T)是一个割,那么f的值不超过割 C U T ( S , T ) CUT(S,T) CUT(S,T)的容量。
  推论二:
  网络中的最大流不超过任何割的容量。
定理二:
  在网络中,如果f是一个流, C U T ( S , T ) CUT (S,T) CUT(S,T)是一个割,且 f f f的值等于割 C U T ( S , T ) CUT(S,T) CUT(S,T)的容量,那么f是一个最大流, C U T ( S , T ) CUT(S,T) CUT(S,T)是一个最小割。
证明:
  令割 C U T ( S , T ) CUT(S,T) CUT(S,T)的容量为C,所以流 f f f的流量也为 C C C。假设另外的任意流 f 1 f1 f1,流量为 c 1 c1 c1,根据流量不超过割的容量,则 c 1 &lt; = c c1&lt;=c c1<=c,所以 f f f是最大流。 假设另外的任意割 C U T ( S 1 , T 1 ) CUT(S1,T1) CUT(S1,T1),容量为 c 1 c1 c1,根据流量不超过割的容量,所以有 c 1 &gt; = c c1&gt;=c c1>=c,故, C U T ( S , T ) CUT(S,T) CUT(S,T)是最小割。

定理结论

在任何的网络中,最大流的值等于最小割的容量 [1] 。
结论一:
  最大流时,最小割cut(S,T)中,正向割边的流量=容量,逆向割边的流量为0。
结论二:
  在最小割cut(S,T)中:

  • 源点s属于S。
  • 如果i属于S,结点j满足:
      有弧<i,j>,并且c [i,j] >f [i,j] ,或者有弧<i,j>,并且f [i,j] >0,那么j属于S。否则不是最小割。 即从s出发能找到的含有残留的点组成集合S,其余的点组成集合T。

(HDU3046)

问题描述
在ZJNU,有一个着名的草原。它吸引了愉快的绵羊和他的同伴去度假。大狼和他的家人都知道这件事,然后悄悄藏在大草坪上。作为ZJNU ACM / ICPC团队,我们有义务保护宜人的绵羊和他的同伴,免受大狼的干扰。我们决定建造一些长度为1的单位围栏。任何狼和羊都不能越过篱笆。当然,一个网格只能包含动物。
现在,我们要求放置最小的栅栏,让喜羊羊和他的同伴免受大狼和他的同伴的打扰。
输入
有很多情况。
对于每种情况:
N和M(N,M <= 200)
然后N * M矩阵:
0是空的,1是愉快的绵羊和他的同伴,2是大狼和他的同伴。
产量
对于每种情况:
第一行输出“Case p:”,p是第p种情况;
第二行是答案。
样本输入
4 6
1 0 0 1 0 0
0 1 1 0 0 0
2 0 0 0 0 0
0 2 0 1 1 0
样本输出
Case 1:
4

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 2e5;
const int inf = 0x3f3f3f3f;
int n, m, s, t, T = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int map[210][210];
struct Edge{
    int v, w, next;
}edge[maxn];
int head[maxn], cnt;
int dis[maxn];
bool vis[maxn];
void init(){
    cnt = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
void add(int u, int v, int w){
    addEdge(u, v, w);
    addEdge(v, u, 0);
}
bool BFS(){
    fill(dis, dis+maxn, inf);
    memset(vis, false, sizeof(vis));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    vis[s] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+1){
                dis[v] = dis[u]+1;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dis[t]!=inf;
}
int DFS(int u, int flow){
    if(u == t)return flow;
    int maxFlow = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int w, v = edge[i].v;
        if(flow > 0 && dis[u]+1==dis[v] && (w = DFS(v, min(flow, edge[i].w)))){
            maxFlow+=w;
            flow-=w;
            edge[i].w-=w;
            edge[i^1].w+=w;
        }
    }
    return maxFlow;
}
void dinic(){
    int maxFlow = 0;
    while(BFS()){
        int w = DFS(s, inf);
        maxFlow+= w;
    }
    
    printf("Case %d:\n%d\n", ++T,maxFlow);
}
int main(){
    while(scanf("%d%d", &n, &m)!=EOF){
        init();
        s = n*m+1, t = s+1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                scanf("%d", &map[i][j]);
                if(map[i][j] == 1){
                    add(s, (i-1)*m+j, inf);
                }
                else if(map[i][j] == 2){
                    add((i-1)*m+j, t, inf);
                }
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                for(int k = 0; k < 4; k++){
                    int tx = i+dir[k][0], ty =  j+dir[k][1];
                    if(tx <= 0 || tx > n || ty <= 0 || ty > m) continue;
                    add((i-1)*m+j, (tx-1)*m+ty, 1);
                }
            }
        }
        dinic();
    }
    return 0;
} 

最大权闭合子图

一个子图(点集), 如果它的所有的出边都在这个子图当中,那么它就是闭合子图。
点权和最大的闭合子图就是最大闭合子图。

首先我们由一道题来引入,见 [线性规划与网络流24题 2] 太空飞行计划问题 。

这道题中,实验依赖于仪器,而实验和仪器都有权值,且仪器为负,实验为正。

这里闭合图的概念就很好引出了。在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。
在这里插入图片描述
上图中闭合图有
{5}、{2,5}、{4,5}
{2,4,5}、{3,4,5}
{1,2,3,4,5}、{1,2,4,5}
最大权闭合图为{3,4,5}。

针对本题而言,我们将实验与仪器间连一条有向边,实验为起点(弧尾),仪器为终点(弧头)。则如果我们选择一个闭合图,那么这个闭合图中包含的实验所需要的仪器也最这个闭合图里。而最大权闭合图即为题目的解。
了解了最大权闭合图的概念,接下来我们就需要知道如何求最大权闭合图。
首先我们将其转化为一个网络(现在不要问为什么,接下来会证明用网络可以求解)。构造一个源点S,汇点T。我们将S与所有权值为正的点连一条容量为其权值的边,将所有权值为负的点与T连一条容量为其权值的绝对值的边,原来的边将其容量定为正无穷。
在这里插入图片描述
上上图即被转化为如上图网络。
首先引入结论,最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图,接下来我们来说明一些结论。

  • 证明:最小割为简单割。
    引入一下简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆)
    那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。

  • 证明网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。
    证明闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。
    证明简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。得正。

  • 证明最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。
    首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。
    则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);(很好理解,不理解画一个图或想象一下就明白了)。
    我们记N这个闭合图的权值和为W。
    则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2);
    则W+C=x1+y1+x2-y2。
    因为明显y1=y2,所以W+C=x1+x2;
    x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。
    所以x1+x2=所有权值为正的点的权值之和(记为TOT).
    所以我们得到W+C=TOT.整理一下W=TOT-C.
    到这里我们就得到了闭合图的权值与简单割的容量的关系。
    因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。得正。

至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。
(HDU1565)

Problem Description
给你一个nn的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。
Input
包括多个测试实例,每个测试实例包括一个整数n 和n
n个非负数(n<=20)
Output
对于每个测试实例,输出可能取得的最大的和
Sample Input
3
75 15 21
75 15 28
34 70 5
Sample Output
188

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn = 2e5;
const int inf = 0x3f3f3f3f;
int n, m, s, t, T = 0;
int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};
int map[210][210];
struct Edge{
    int v, w, next;
}edge[maxn];
int head[maxn], cnt;
int dis[maxn];
bool vis[maxn];
void init(){
    cnt = 0;
    memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w){
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt++;
}
void add(int u, int v, int w){
    addEdge(u, v, w);
    addEdge(v, u, 0);
}
bool BFS(){
    fill(dis, dis+maxn, inf);
    memset(vis, false, sizeof(vis));
    queue<int> q;
    q.push(s);
    dis[s] = 0;
    vis[s] = true;
    while(!q.empty()){
        int u = q.front();
        q.pop();
        vis[u] = false;
        for(int i = head[u]; i != -1; i = edge[i].next){
            int v = edge[i].v;
            if(edge[i].w && dis[v] > dis[u]+1){
                dis[v] = dis[u]+1;
                if(!vis[v]){
                    q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dis[t]!=inf;
}
int DFS(int u, int flow){
    if(u == t)return flow;
    int maxFlow = 0;
    for(int i = head[u]; i != -1; i = edge[i].next){
        int w, v = edge[i].v;
        if(flow > 0 && dis[u]+1==dis[v] && (w = DFS(v, min(flow, edge[i].w)))){
            maxFlow+=w;
            flow-=w;
            edge[i].w-=w;
            edge[i^1].w+=w;
        }
    }
    return maxFlow;
}
int dinic(){
    int maxFlow = 0;
    while(BFS()){
        int w = DFS(s, inf);
        maxFlow+= w;
    }
    return maxFlow;
}
int main(){
    while(scanf("%d", &n)!=EOF){
        init();
        int sum = 0;
        s = n*n+1, t = s+1;
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                scanf("%d", &map[i][j]);
                sum+=map[i][j];
                if((i+j) % 2 == 0){
                    add(s, (i-1)*n+j, map[i][j]);
                }
                else{
                    add((i-1)*n+j, t, map[i][j]);
                }
            }
        }
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= n; j++){
                if ((i + j) % 2 == 0){
                    for(int k = 0; k < 4; k++){
                        int tx = i+dir[k][0], ty =  j+dir[k][1];
                        if(tx <= 0 || tx > n || ty <= 0 || ty > n) continue;
                        add((i-1)*n+j, (tx-1)*n+ty, inf);
                    }
                }
            }
        }
        printf("%d\n", sum-dinic());
    }
    return 0;
} 

有上下界网络流

问题模型:

给定一个加权的有向图,满足:
(1)容量限制条件: b ( u , v ) ≤ f ( u , v ) ≤ c ( u , v ) b(u, v)\leq f(u, v) \leq c(u,v) b(u,v)f(u,v)c(u,v)
(2)流量平衡条件: ∑ ( u , w ) f ( u , w ) = ∑ ( w , u ) ∈ E f ( w , v ) \sum_{(u,w)}f(u,w) = \sum_{(w,u)\in E}f(w,v) (u,w)f(u,w)=(w,u)Ef(w,v)

  • (2)中的 w ∈ V − ( s , t ) w\in V-(s,t) wV(s,t) 即除了源汇外,所有点都满足流量平衡条件,则称G为有源汇网络;否则,即不存在源汇,所有点都满足流量平衡条件,则称G为无源汇网络。

将这类问题由易到难一一解决:

问题[1] 求无源汇的网络有上下界的可行流

给定条件:
一个网络中无源汇点,且每条边的流量满足给定特定条件下的上下界限制。
求任意一个满足条件的可行流,给出可行流中每条边的流量大小。
现在只需求出任意满足以上条件的流,即为该网络的可行流。

在这里插入图片描述
由于下界是一条弧上的流必需要满足的确定值。下面引入必要弧的概念:必要弧是一定流要满的弧。必要弧的构造,将容量下界的限制分离开了,从而构造了一个没有下界的网络G’:
1. 将原弧(u,v)分离出一条必要弧: c ‘ ( u , v ) = b ( u , v ) c`(u,v)=b(u,v) c(u,v)=b(u,v)。(红色表示)
2. 原弧: c ‘ ( u , v ) = c ( u , v ) − b ( u , v ) c`(u,v) = c(u,v)-b(u,v) c(u,v)=c(u,v)b(u,v)
在这里插入图片描述
由于必要弧的有一定要满的限制,将必要弧“拉”出来集中考虑:
在这里插入图片描述
添加附加源x, 附加汇y。想像一条不限上界的(y, x),用必要弧将它们“串”起来,即对于有向必要弧(u, v),添加(u, y),(x, v),容量为必要弧容量。这样就建立了一个等价的网络。
在这里插入图片描述
一个无源汇网络的可行流的方案一定是必要弧是满的。若去掉(y, x)后,附加源x到附加汇y的最大流,能使得x的出弧或者y的入弧都满,充要于原图有可行流。
在这里插入图片描述
算法:

  1. 按上述方法构造新网络(分离必要弧,附加源汇)
  2. 求附加源x到附加汇y的最大流
  3. 若x的出弧或y的入弧都满,则有解,将必要弧合并回原图;否则,无解。
  4. 根据其残留网络,对每条管子的剩余,最大容量Cij-残余ij就是当前管子的实际流量。

(SGU 194)

Problem Description
由一位着名的国际恐怖分子本·布拉登领导的恐怖组织正在建造一座核反应堆,为他们计划制造的核弹生产钚。作为这个群体中邪恶的计算机天才,您负责开发反应堆的冷却系统。
反应堆的冷却系统由特殊冷却液流过的管道数量组成。管道连接在特殊点,称为节点,每个管道都有起始节点和终点。液体必须通过管道从其起始点流向其终点而不是沿相反方向流动。
让节点从1到N编号。冷却系统必须设计成液体通过管道循环,进入每个节点的液体量(以时间为单位)等于离开的液体量节点。也就是说,如果我们将管道从第i个节点到第j个指定的液体量指定为 f i j f_{ij} fij,( 如果没有从节点i到节点j的管道,则将 f i j f_{ij} fij = 0),对于每个i,必须符合以下条件:
∑ j = 1 N f i j = ∑ j = 1 N f j i \sum_{j = 1}^Nf_{ij}= \sum_{j = 1}^Nf_{ji} j=1Nfij=j=1Nfji
每个管道有一定的有限的容量,因此对于每个i和j由配管连接,必须为 f i j ≤ c i j f_{ij} ≤c_{ij} fijcij其中 c i j c_{ij} cij 是管的容量。为了提供足够的冷却,通过管道从第i个要j个节点中流动的液体的量必须至少为 l i j l_{ij} lij,从而它必须是 f i j ≥ l i j f_{ij} ≥l_{ij} fijlij
给定 所有管道的 c i j c_{ij} cij l i j l_{ij} lij,找到量 f i j f_{ij} fij,满足上面指定的条件。
输入格式:
输入文件的第一行包含数字N(1≤N≤200) - 节点数和M - 管道数。以下M行每个包含四个整数–i,j, l i j l_{ij} lij c i j c_{ij} cij 。有至多一个管连接的任意两个节点和 0 ≤ l i j ≤ c i j ≤ 1 0 5 0≤l_{ij} ≤c_{ij} ≤10^5 0lijcij105 对所有管道。没有管道将节点连接到自身。如果存在从第i个节点到第j个节点的管道,则没有从第j个节点到第i个节点的管道。
输出格式:
如果有方法进行反应器冷却,则在输出文件的第一行打印YES,如果没有,则打印NO。在第一种情况下,M个整数必须跟随,第k个数字是第k个管道流动的液体量。管道的编号与输入文件中的编号相同。
input
4 6
1 2 1 3
2 3 1 3
3 4 1 3
4 1 1 3
1 3 1 3
4 2 1 3
output
YES
1
2
3
2
1
1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue> 
using namespace std;
inline int read(){
    int t=1,num=0;
    char c=getchar();
    while(c>'9'||c<'0'){if(c=='-')t=-1;c=getchar();}
    while(c>='0'&&c<='9'){num=num*10+c-'0';c=getchar();}
    return num*t;
}
const int inf = 0x3f3f3f3f;
const int maxn = 1e5;
const int maxm = 500;
int n, m, s, t;
struct Edge{
	int v, w, c, next;
}edge[maxn];
int head[maxm], cnt;
int dis[maxm];
int in[maxm];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
	memset(in, 0, sizeof(in));
}
void addEdge(int u, int v, int w, int c){
	edge[cnt].v = v;
	edge[cnt].w = w;
	edge[cnt].c = c;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}
void add(int u, int v, int w, int c){
	addEdge(u, v, w, c);
	addEdge(v, u, 0, c);
}
bool BFS(){
	memset(dis, -1, sizeof(dis));
	queue<int> que;
	que.push(s);
	dis[s] = 0;
	while(!que.empty()){
		int u = que.front();
		que.pop();
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].v;
			if(dis[v]==-1 && edge[i].w){
				dis[v] = dis[u]+1;
				que.push(v);
			}
		}
	}
	return dis[t] != -1;
}
int DFS(int u, int flow){
	if(u == t) return flow;
	for(int i = head[u]; i != -1; i = edge[i].next){
		int v = edge[i].v, w;
		if(dis[v] == dis[u]+1 && edge[i].w && (w = DFS(v, min(flow, edge[i].w)))){
			edge[i].w-=w;
			edge[i^1].w+=w;
			return w;
		}
	}
	dis[u] = -1;
	return 0;
}
int dinic(){
	int maxFlow = 0, w;
	while(BFS()){
		while(1){
			w = DFS(s, inf);
			if(w==0)break;
			maxFlow += w;
		}
	}
	return maxFlow;
}
int main(){
	int T;
	T = read();
	while(T--){
		init();
		n = read(), m = read();
		s = n+1, t = s+1;
		int sum = 0;
		int idx1 = cnt;
		for(int i = 1; i <= m; i++){
			int u, v, l, r;
			u = read(), v = read(), l = read(), r = read();
			add(u, v, r-l, r);
			in[v] += l;
			in[u] -= l;
		}
		int idx2 = cnt;
		for(int i = 1; i < s; i++){
			if(in[i] > 0) add(s, i, in[i], inf);
			else if(in[i] < 0){
				add(i, t, -in[i], inf);
				sum+=-in[i];
			}
		}
		int ans = dinic();
		if(ans != sum){
			printf("NO\n");
		}
		else{
			printf("YES\n");
            for(int i=idx1;i<idx2;i+=2){
            	printf("%d\n",edge[i].c-edge[i].w);
			}
		}
	}
	return 0;
} 

问题[2] 求有源汇的网络有上下界的可行流

给定条件:
一个网络中存在源汇点S,T,且每条边的流量满足给定特定条件下的上下界限制。
求任意一个满足条件的可行流。给出可行流中每条边的流量,以及网络 S → T 总 流 量 。 S\rightarrow T总流量。 ST

如果能把有源汇转为无源汇,回归到上一个问题,是不是就简单很多了呢?
只需要加一条从T到S的流量上限为无穷大的边即可。如果你已经掌握了上一个问题的解决方法,就可以轻松的解决了。
转化为无源汇点可行流后,每个点是流量守恒的,对于S,T同样成立。那么除了可以求解原网络上每条边的流量大小外,还可以通过记录T到S的流入量来给出原有源汇点的网络总流量。因为总流量为outs,而无源汇图中ins = outs,而S作为入度仅连接了 T → S T\rightarrow S TS这一条边,那么这一条有向边的反向边上的flow值自然就等于原图的总流量。

问题[3]求有源汇的网络有上下界的最大流

算法:

  1. 先转化为问题[2]来求解一个可行流。若可行无解,则退出。由于必要弧是分离出来的,所以就可以把必要弧(附加源汇及其临边)及其上的流,暂时删去。再将(T,S)删去,恢复源汇。
  2. 再次,从S到T找增广轨,求最大流。
  3. 最后将暂时删去的下界信息恢复,合并到当前图中。输出解。

(ZOJ3229)

Gensokyo is a world which exists quietly beside ours, separated by a mystical border. It is a utopia where humans and other beings such as fairies, youkai(phantoms), and gods live peacefully together. Shameimaru Aya is a crow tengu with the ability to manipulate wind who has been in Gensokyo for over 1000 years. She runs the Bunbunmaru News - a newspaper chock-full of rumors, and owns the Bunkachou - her record of interesting observations for Bunbunmaru News articles and pictures of beautiful danmaku(barrange) or cute girls living in Gensokyo. She is the biggest connoisseur of rumors about the girls of Gensokyo among the tengu. Her intelligence gathering abilities are the best in Gensokyo!
During the coming n days, Aya is planning to take many photos of m cute girls living in Gensokyo to write Bunbunmaru News daily and record at least Gx photos of girl x in total in the Bunkachou. At the k-th day, there are Ck targets, Tk1, Tk2, …, TkCk. The number of photos of target Tki that Aya takes should be in range [Lki, Rki], if less, Aya cannot write an interesting article, if more, the girl will become angry and use her last spell card to attack Aya. What’s more, Aya cannot take more than Dk photos at the k-th day. Under these constraints, the more photos, the better.
Aya is not good at solving this complex problem. So she comes to you, an earthling, for help.
Input
There are about 40 cases. Process to the end of file.
Each case begins with two integers 1 <= n <= 365, 1 <= m <= 1000. Then m integers, G1, G2, …, Gm in range [0, 10000]. Then n days. Each day begins with two integer 1 <= C <= 100, 0 <= D <= 30000. Then C different targets. Each target is described by three integers, 0 <= T < m, 0 <= L <= R <= 100.
Output
For each case, first output the number of photos Aya can take, -1 if it’s impossible to satisfy her needing. If there is a best strategy, output the number of photos of each girl Aya should take at each day on separate lines. The output must be in the same order as the input. If there are more than one best strategy, any one will be OK.
Output a blank line after each case.
Sample Input
2 3
12 12 12
3 18
0 3 9
1 3 9
2 3 9
3 18
0 0 3
1 3 6
2 6 9
Sample Output
36
9
6
3
3
6
9

题意:
给定N天,有M个女孩要上报纸。每个人至少被拍Gi张照片。
每天拍照总数不超过Di。
每天要给Ci个女孩拍照,每个女孩拍照的张数限定在[L, R]内。
求最多的照片总数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int inf = 1<<30;
const int maxn = 1e6;
const int maxm = 2000;
int n, m, s, t, G;
struct Edge{
	int v, w, next;
}edge[maxn];
int head[maxm], cnt;
int dis[maxm];
int in[maxm],low[555][1111],flag[555][1111];
void init(){
	cnt = 0;
	memset(head, -1, sizeof(head));
	memset(in, 0, sizeof(in));
	memset(low, 0, sizeof(low));
	memset(flag, 0, sizeof(flag));
}
void addEdge(int u, int v, int w){
	edge[cnt].v = v;
	edge[cnt].w = w;
	edge[cnt].next = head[u];
	head[u] = cnt++;
}
void add(int u, int v, int w){
	addEdge(u, v, w);
	addEdge(v, u, 0);
}
bool BFS(){
	memset(dis, -1, sizeof(dis));
	queue<int> que;
	que.push(s);
	dis[s] = 0;
	while(!que.empty()){
		int u = que.front();
		que.pop();
		for(int i = head[u]; i != -1; i = edge[i].next){
			int v = edge[i].v;
			if(dis[v]==-1 && edge[i].w){
				dis[v] = dis[u]+1;
				que.push(v);
			}
		}
	}
	return dis[t] != -1;
}
int DFS(int u, int flow){
	if(u == t) return flow;
	for(int i = head[u]; i != -1; i = edge[i].next){
		int v = edge[i].v, w;
		if(dis[v] == dis[u]+1 && edge[i].w && (w = DFS(v, min(flow, edge[i].w)))){
			edge[i].w-=w;
			edge[i^1].w+=w;
			return w;
		}
	}
	dis[u] = -1;
	return 0;
}
int dinic(){
	int maxFlow = 0, w;
	while(BFS()){
		while(1){
			w = DFS(s, inf);
			if(w==0)break;
			maxFlow += w;
		}
	}
	return maxFlow;
}
int main(){
	while(~scanf("%d%d", &n, &m)){
		int x, y, sum;
		init();
		s = n+m+1;
		t = s+1;
		for(int i = 1; i <= m; i++){
			int d;
			scanf("%d", &d);
			add(n+i, t, inf-d);
			in[t] += d;
			in[n+i] -= d;
		}
		for(int i = 1; i <= n; i++){
			int c, G;
			scanf("%d%d", &c, &G);
			add(s, i, G);
			for(int j = 1; j <= c; j++){
				int k, l, r;
				scanf("%d%d%d", &k, &l, &r);
				add(i, n+k+1, r-l);
				in[n+k+1] += l;
				in[i] -= l;
				low[i][k+1]=l;
				flag[i][k+1]=cnt-2;
			}
		}
		x = t+1, y = t+2, sum = 0;
		for(int i = 1; i <= t; i++){
			if(in[i] > 0){
				add(x, i, in[i]);
				sum+=in[i];
			}
			else if(in[i] < 0){
				add(i, y, -in[i]);
			}
		}
		add(t, s, inf);
		s = x, t = y;
		int ans = dinic();
		if(ans!=sum){
			printf("-1\n");
		}
		else{
			s = n+m+1;
			t = s+1;
			ans=dinic();
			printf("%d\n", ans);
			for(int i = 1 ; i <= n ; i++){
		        for(int j = 1 ; j <= m ; j++){
		         	if(flag[i][j]){
		         	 	printf("%d\n",edge[flag[i][j]^1].w+low[i][j]);
		         	}
		        }
		    }
		}
		puts("");
	}
	return 0;
} 

问题[4]求有源汇的网络有上下界的最小流

给定条件:
一个网络中存在源汇点S,T,且每条边的流量满足给定特定条件下的上下界限制。
求满足给定条件的S−>T 最小流。

分析:

  • 增加超级源点st和超级汇点sd,对于有上下界的边(i,j)流量(L,R)变为R-L,然后i与sd连接容量是L,st与j连接容量是L;网络中规定不能有流量流入st,也不能有流量流入sd;
  • 做一次最大流Dinic;
  • 在汇点sd到st连一条容量是inf的边;
  • 在做一次最大流Dinic
  • 当且仅当附加弧都满流是有可行流,最后的最小流是flow[sd->st]^1],st到sd的最大流就是sd到st的最小流;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值