基本概念与常用定理
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中每条弧 < u , v > <u,v> <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,V3…t),如果满足 s s s是源点, t t t是汇点,并且序列中每相邻两个顶点之间均存在一条弧,那么就称这个顶点序列为一条链(注意这里并不要求这条弧的方向一定与有向图中的方向一致)。
弧:在链中,弧被分为前向弧和后向弧,前向弧指在链中顶点的顺序与容量网络中弧的方向一致的弧,而后向弧则是方向不一致的弧。例如对于下图而言,
(
A
,
D
,
B
,
C
)
(A,D,B,C)
(A,D,B,C)也是一条链,但是其中
<
A
,
D
>
、
<
B
,
C
>
<A,D>、<B,C>
<A,D>、<B,C>是前向弧,
<
D
,
B
>
<D,B>
<D,B>是后向弧。
增广路:对于可行流的一条链 P P P,如果满足:
- P P P中所有的前向弧的流量小于容量;
- P P P中所有的后向弧的流量均大于零;
那么这条链 P P P就被称作增广路。
残留容量 :给定容量网络 G ( V , E ) G(V,E) G(V,E),及可行流 f f f,弧 < u , v > <u,v> <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的流量增加,所以每条弧 < u , v > <u,v> <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 S−T割。
割的割边:如果一条弧的两个顶点一个属于顶点集S一个属于顶点集T,该弧为割
S
−
T
S-T
S−T割的一条割边。例如对于上图,将顶点划分为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 、 < u , v > ∈ E ) C(S,T)=\sum c(u,v)(u\in S、v \in T、<u,v>\in E) C(S,T)=∑c(u,v)(u∈S、v∈T、<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)的每条边
<
i
,
j
>
<i,j>
<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,如下三个命题等价:
- 存在一个割使得割的容量 c = f c=f c=f;
- . f f f是最大流的流量;
- G G G中不存在任何增广路;
最大流最小割定理:在任何网络中,最大流的值等于最小割的容量。
Dinic算法
1.求解最大流
Dinic算法求解最大流分为以下几个步骤:
- b f s bfs bfs分层;
- d f s dfs dfs增广;
- 重复执行 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算法求解最小费用最大流分为以下几个步骤:
- s p f a spfa spfa分层;
- d f s dfs dfs增广;
- 重复执行 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;
}