专题 网络流从放弃到坚持放弃

1 篇文章 0 订阅
0 篇文章 0 订阅

基本概念与常用定理

1. 基本概念

1.1 网络流

网络流图:带权的有向图 G = ( V , E ) G=(V,E) G=(V,E),满足以下条件,则称为网络流图 ( f l o w n e t w o r k ) (flow network) (flownetwork)

  • 仅有一个入度为 0 0 0的顶点 s s s,称 s s s为源点;
  • 仅有一个出度为 0 0 0的顶点 t t t,称 t t t为汇点;
  • 每条边的权值都为非负数,称为该边的容量,记作 w ( i , j ) w(i,j) w(i,j)

弧的流量:通过容量网络 G G G中每条弧 &lt; u , v &gt; &lt;u,v&gt; <u,v>上的实际流量(简称流量),记为 f ( u , v ) f(u,v) f(u,v)

网络流:所有弧上流量的集合 f = f ( u , v ) f={f(u,v)} f=f(u,v),称为该容量网络的一个网络流

可行流:首先每条弧上的流量不能超过其容量,还有对于除了源点和汇点以外的每个点来说,流入它的流量之和必须与从它流出的流量之和相等,即平衡条件。那么满足这两个条件的网络流就被称作是可行流

可行流的流量:可行流从源点所流出的所有流量之和。

最大流:在所有的可行流中,流量最大的那个被称作是最大流

:对于一串顶点序列 ( s , v 1 , v 2 , V 3 … t ) (s,v_1,v_2,V_3…t) (s,v1,v2,V3t),如果满足 s s s是源点, t t t是汇点,并且序列中每相邻两个顶点之间均存在一条弧,那么就称这个顶点序列为一条(注意这里并不要求这条弧的方向一定与有向图中的方向一致)。

:在链中,弧被分为前向弧后向弧,前向弧指在链中顶点的顺序与容量网络中弧的方向一致的弧,而后向弧则是方向不一致的弧。例如对于下图而言, ( A , D , B , C ) (A,D,B,C) (A,D,B,C)也是一条链,但是其中 &lt; A , D &gt; 、 &lt; B , C &gt; &lt;A,D&gt;、&lt;B,C&gt; <A,D><B,C>是前向弧, &lt; D , B &gt; &lt;D,B&gt; <D,B>是后向弧。
在这里插入图片描述

增广路:对于可行流的一条链 P P P,如果满足:

  1. P P P中所有的前向弧的流量小于容量;
  2. P P P中所有的后向弧的流量均大于零;

那么这条链 P P P就被称作增广路

残留容量 :给定容量网络 G ( V , E ) G(V,E) G(V,E),及可行流 f f f,弧 &lt; u , v &gt; &lt;u,v&gt; <u,v>上的残留容量记为 c l ( u , v ) = c ( u , v ) − f ( u , v ) cl(u,v)=c(u,v)-f(u,v) cl(u,v)=c(u,v)f(u,v)。每条弧上的残留容量表示这条弧上可以增加的流量。因为从顶点 u u u到顶点 v v v的流量减少,等效与从顶点 v v v到顶点 u u u的流量增加,所以每条弧 &lt; u , v &gt; &lt;u,v&gt; <u,v>上还有一个反方向的残留容量 c l ( v , u ) = − f ( u , v ) cl(v,u)=-f(u,v) cl(v,u)=f(u,v)

残余网络:在一个网络流图上,找到一条源到汇的路径(即找到了一个流量)后,对路径上所有的边,其容量都减去此次找到的量,对路径上所有的边,都添加一条反向边,其容量也等于此次找到的流量,这样得到的新图,就称为原图的“残余网络

1.2 割

网络流的割(割集):是网络中顶点的一个划分,把所有顶点划分成两个顶点集合 S S S T T T,其中源点 s s s属于 S S S,汇点 t t t属于 T T T S S S T T T的这样一个割就是 S − T S-T ST割。

割的割边:如果一条弧的两个顶点一个属于顶点集S一个属于顶点集T,该弧为割 S − T S-T ST割的一条割边。例如对于上图,将顶点划分为S=(A,B)、T=(C,D)S=(A,B)、T=(C,D)的这样一个割就是S-T割。从S指向T的割边是正向割边,从T指向S的割边是逆向割边
打个比方:割集好比是一个恐怖分子,把你家和自来水厂之间的水管网络砍断了一些,然后自来水厂无论怎么放水,水都只能从水管断口哗哗流走了,你家就停水了。割的大小应该是恐怖分子应该关心的事 毕竟细管子好割一些,而最小割花的力气最小。

割的容量:用 C ( S , T ) C(S,T) C(S,T)表示, C ( S , T ) = ∑ c ( u , v ) ( u ∈ S 、 v ∈ T 、 &lt; u , v &gt; ∈ E ) C(S,T)=\sum c(u,v)(u\in S、v \in T、&lt;u,v&gt;\in E) C(S,T)=c(u,v)(uSvT<u,v>E)( E E E代表容量网络所有弧的集合)。例如对上图而言,割 S = ( A , B ) , T = ( C , D ) S=(A,B),T=(C,D) S=(A,B),T=(C,D)的容量为 1 + 5 + 3 = 9 1+5+3=9 1+5+3=9。而对于割 S = ( A , D ) , T = ( B , C ) S=(A,D),T=(B,C) S=(A,D),T=(B,C),它的容量为: 4 + 8 = 12 4+8=12 4+8=12

最小割:在所有的割中,容量最小的割被称作最小割

割的流量:指的是前向弧的流量之和减去后向弧的流量之和。因此割的流量小于等于割的容量,当且仅当割所划分的两个点集中不存在后向弧时取等。

1.3 费用流

费用流费用流即在网络流的基础上,给图 G = ( V , E ) G=(V,E) G=(V,E)的每条边 &lt; i , j &gt; &lt;i,j&gt; <i,j>加上了费用 c o s t ( i , j ) cost(i,j) cost(i,j),网络流的总费用为 a l l c o s t = ∑ ( i , j ) ∈ E c o s t ( i , j ) × f l o w ( i , j ) allcost=\sum_{(i,j)\in E}cost(i,j)\times flow(i,j) allcost=(i,j)Ecost(i,j)×flow(i,j)
注意:这里的费用指的是单位流量的费用,计算时用每条边的费用乘以每条边的流量,而非每条边的费用只计算一次。

最小费用最大流:在流量最大的前提下,达到所用的费用最小的可行流。

2. 常用定理

定理1:对于可行流的任意一个割,割的流量 = = =可行流的流量。

定理2:可行流的流量一定小于等于任意一个割的容量。

定理3:对于可行流 G G G,设其流量为 f f f,如下三个命题等价:

  1. 存在一个割使得割的容量 c = f c=f c=f
  2. . f f f是最大流的流量;
  3. G G G中不存在任何增广路;

最大流最小割定理:在任何网络中,最大流的值等于最小割的容量。

Dinic算法

1.求解最大流

Dinic算法求解最大流分为以下几个步骤:

  1. b f s bfs bfs分层;
  2. d f s dfs dfs增广;
  3. 重复执行 1.2. 1.2. 1.2.操作,直到图中无增广路为止。
#include <bits/stdc++.h>
using namespace std;

const int MAXN=10005;
const int MAXM=100005;
const int INF=1e9+7;
int n,m,s,t;
int dis[MAXN];

int size=0;
int head[MAXN],cur[MAXN];
struct EDGE
{
	int u;
	int v;
	int w;
	int next;
}edge[MAXM<<1];

inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=1; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}

inline void addedge(int u,int v,int w)
{
	edge[size].u=u;
	edge[size].v=v;
	edge[size].w=w;
	edge[size].next=head[u];
	head[u]=size++;
}

bool bfs()
{
	memset(dis,-1,sizeof(dis));
	dis[t]=0;
	queue <int> q;
	q.push(t);
	while(!q.empty())
	{
		int u=q.front(); q.pop();
		for(int i=head[u];~i;i=edge[i].next)
		{
			if(dis[edge[i].v]==-1&&edge[i^1].w>0)
			{
				dis[edge[i].v]=dis[u]+1;
				q.push(edge[i].v);
			}
		}
	}
	return ~dis[s];
}

int dfs(int u,int flow)
{
	if(u==t||flow==0) return flow;
	int data=flow;
	for(int &i=cur[u];~i;i=edge[i].next)
	{
		if(dis[u]==dis[edge[i].v]+1&&edge[i].w>0)
		{
			int d=dfs(edge[i].v,min(data,edge[i].w));
			edge[i].w-=d;
			edge[i^1].w+=d;
			data-=d;
			if(!data) break;			
		}
	}
	return flow-data;
}

int dinic()
{
	int ans=0;
	while(bfs())
	{
		for(int i=1;i<=n;++i)
		    cur[i]=head[i];
		ans+=dfs(s,INF);
	}
	return ans;
}

void readdata()
{
	memset(head,-1,sizeof(head));
	n=read(); m=read(); s=read(); t=read();
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read(),w=read();
		addedge(u,v,w);
		addedge(v,u,0);
	}
}

void work()
{
	printf("%d\n",dinic());
}

int main()
{
	readdata();
	work();
	return 0;
} 

2.求解最小费用最大流

Dinic算法求解最小费用最大流分为以下几个步骤:

  1. s p f a spfa spfa分层;
  2. d f s dfs dfs增广;
  3. 重复执行 1.2. 1.2. 1.2.操作,直到图中无增广路为止。
#include <bits/stdc++.h>
using namespace std;

const int MAXN=5005;
const int MAXM=50005;
const int INF=0x3f3f3f3f;
int n,m,s,t,mincost=0,maxcap=0;
int dis[MAXN];
bool used[MAXN];

int size=0;
int head[MAXN],cur[MAXN];
struct EDGE
{
	int u;
	int v;
	int cap;//残量 
	int cost;
	int next;
}edge[MAXM<<1];

inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=1; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+(ch^'0'); ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}

inline void addedge(int u,int v,int cap,int cost)
{
	edge[size].u=u;
	edge[size].v=v;
	edge[size].cap=cap;
	edge[size].cost=cost;
	edge[size].next=head[u];
	head[u]=size++;
}

struct cmp
{
	bool operator () (const int a,const int b)
	{
		return dis[a]>dis[b];
	}
};

bool spfa()
{
    priority_queue<int,vector<int>,cmp> q;
	memset(dis,0x3f,sizeof(dis));
	memset(used,0,sizeof(used));
	dis[t]=0;
	used[t]=1;
	q.push(t);
	while(!q.empty())
	{
		int u=q.top(); q.pop();
		used[u]=0;
		for(int i=head[u];~i;i=edge[i].next)
		{
			int cap=edge[i^1].cap;
			int cost=edge[i^1].cost;
			if(cap>0&&dis[edge[i].v]>dis[u]+cost)
			{
				dis[edge[i].v]=dis[u]+cost;
				if(!used[edge[i].v])
				{
					q.push(edge[i].v);
					used[edge[i].v]=1;
				}
			}
		}
	}
	return dis[s]<INF;
}

int dfs(int u,int flow)
{
	if(u==t||flow==0) return flow;
	int data=flow;
	used[u]=1;
	for(int &i=cur[u];~i;i=edge[i].next)
	{
		int cap=edge[i].cap;
		int cost=edge[i].cost;
		if(cap>0&&!used[edge[i].v]&&dis[u]==dis[edge[i].v]+cost)
		{
			int d=dfs(edge[i].v,min(cap,data));
			mincost+=d*cost;
			data-=d;
			edge[i].cap-=d;
			edge[i^1].cap+=d;
			if(data==0) break;
		}
	}
	return flow-data;
}

void readdata()
{
	memset(head,-1,sizeof(head));
	n=read(); m=read(); s=read(); t=read();
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read(),cap=read(),cost=read();
		addedge(u,v,cap,cost);
		addedge(v,u,0,-cost);
	}
}

void work()
{
	while(spfa())
	{
		memset(used,0,sizeof(used));
		for(int i=1;i<=n;++i)
		    cur[i]=head[i];
		maxcap+=dfs(s,INF);
	}
	printf("%d %d",maxcap,mincost);
}

int main()
{
	freopen("input.txt","r",stdin);
	readdata();
	work();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值