网络流算法。
本文部分参考这篇博客
首先便是定义。
两个特殊节点源(s)与汇(t),两者都是有且仅有一个;弧;弧的容量c(u,v);弧上流量f(u,v);链;割。
弧分为很多类型。
链的定义:在容量网络中,称一个顶点序列为链应该满足任意相邻两个点之间都有弧相连,但是弧的方向不一定都要和链的方向相同。
割的定义:对于网络流图的一个子集,如果它把点分为了两个部分,那么那些从一部分到另一部分的边所组成的集合,就是割。
再是性质。
关于流的定义。
再是定理。
最大流最小割定理:最大流=最小割。由此可以得知最大流小于等于任意割的容量。
接下来我们就要看看这最大流应该怎么求呢?
增广路:如果图中的一条链满足以下要求:链中所有前向弧(与链方向相同的弧)都是非饱和弧,并且后向弧(与链方向相反的弧)都是非零的弧,那么该链就是关于可行流f的一条增广路。
如果一个可行流不是最大流,那么当前网络图中一定
还存在增广路。
增广:沿着增广路改进可行流的操作。
引入残余网络和残留容量。
残留容量:
对于每一条弧上的残留容量记 c f ( u , v ) = c ( u , v ) − f ( u , v ) cf(u,v)=c(u,v)-f(u,v) cf(u,v)=c(u,v)−f(u,v),也就是这条弧上最多还能通过的流量。字面意思理解即可。当然还有个反向残留容量 c f ( v , u ) = − f ( u , v ) cf(v,u)=-f(u,v) cf(v,u)=−f(u,v)。
残余网络:
每找到一条源到汇的路径后将路径上的弧减去路径上最小的弧的容量,反向边上增加这个容量,得到的新图就是原图的残余网络。
要介绍的东西没了,下面就可以进入正题了。
费用流就是在网络流基础上给每条边都加上了费用cost,该网络的总费用就是 ∑ u , v ∈ E f u , v ∗ c o s t u , v \sum_{u,v\in E}f_{u,v}*cost_{u,v} ∑u,v∈Efu,v∗costu,v
下面就是喜闻乐见的算法了。
现在引入反向边。
反向边是干嘛的呢?你每次找到一条增广路,便直接增广了,想想便会发现问题,如果它卡了其他的路呢?答案就变小了,这个反向边就是给程序一个反悔的机会,而不是采用回溯,不然时间复杂度就直接上升到了一定境界。
那么反向边是怎么使用的呢?我们在找到一条增广路后,将路径上的边全部减去路径上的边的最小值,然后在这条边的反向边上加上这个值即可。
这里便直接讲解Dinic算法了。
没错这便是今天的主题了。
Dinic算法将整张图按照路径长度分为了若干层,那么很显然,找的增广路便是满足所有的点在不同的层。
下面看看它的时间复杂度。
在普通情况下,复杂度为 O ( v 2 e ) \mathcal{O}(v^2e) O(v2e)。
这边可以注意到,这些优化并没有优化时间复杂度,但是实际情况下可以快上不少。
我们每次都要对反向弧操作,因此我们的边标号从0开始,这样每次只需要异或一下即可,方便快捷。
另外一说:网络流全部的难点在于建图,这意味着只会背模板也是可以接受的,所以优化在一开始就加足了,是很有帮助的。
下面附上我的模板代码QWQ,可能有点丑,能看就行了对吧。
模板题目地址。
#include
using std::cin;
using std::cout;
using std::endl;
int S,T;
int n,m;
int head[200001],tot=1;
std::queue<int>q;
int deep[200001];
int cf[500001];
struct edge{
int to;
int nxt;
}e[500002];
void add(int x,int y,int z){
e[++tot].to=y;
e[tot].nxt=head[x];
cf[tot]=z;
head[x]=tot;
}
bool bfs(){
memset(deep,0,sizeof(deep));
while(!q.empty())
q.pop();
q.push(S);
deep[S]=1;
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if(cf[i]&&!deep[y]){
deep[y]=deep[now]+1;
q.push(y);
}
}
}
return deep[T];
}
int dfs(int now,int min){
if(now==T)
return min;
for(int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if(deep[y]==deep[now]+1&&cf[i]){
int w=dfs(y,std::min(min,cf[i]));
if(w){
cf[i]-=w;
cf[i^1]+=w;
return w;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
while(int w=dfs(S,0x3f3f3f3f))
ans+=w;
}
return ans;
}
main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>S>>T;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,0);
}
cout<<Dinic();
return 0;
}
下面是加了当前弧优化的模板,其实两个优化同时加区别并不是很大。
#include
using std::cin;
using std::cout;
using std::endl;
int S,T;
int n,m;
int head[200001],tot=1;
std::queue<int>q;
int deep[200001];
int cf[500001];
int last[500001];
struct edge{
int to;
int nxt;
}e[500002];
void add(int x,int y,int z){
e[++tot].to=y;
e[tot].nxt=head[x];
cf[tot]=z;
head[x]=tot;
}
bool bfs(){
memset(deep,0,sizeof(deep));
while(!q.empty())
q.pop();
q.push(S);
deep[S]=1;
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=head[now];i;i=e[i].nxt){
int y=e[i].to;
if(cf[i]&&!deep[y]){
deep[y]=deep[now]+1;
q.push(y);
}
}
}
return deep[T];
}
int dfs(int now,int min){
if(now==T)
return min;
for(int &i=last[now];i;i=e[i].nxt){
int y=e[i].to;
if(deep[y]==deep[now]+1&&cf[i]){
int w=dfs(y,std::min(min,cf[i]));
if(w){
cf[i]-=w;
cf[i^1]+=w;
return w;
}
}
}
return 0;
}
int Dinic(){
int ans=0;
while(bfs()){
for(int i=1;i<=n;++i)
last[i]=head[i];
while(int w=dfs(S,0x3f3f3f3f))
ans+=w;
}
return ans;
}
main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>S>>T;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,0);
}
cout<<Dinic();
return 0;
}
第二个模板:最小费用最大流。
直接上最短路,在最短路上增广即可。
#include
int head[100001],tot=1;
int n,m;
int S,T;
int maxflow,mincost;
struct edge{
int to;
int nxt;
int flow;
int cost;
}e[200001];
int dis[100001];
int flow[100001];
int vis[100001];
int last[100001];
int Last[100001];
void add(int x,int y,int Flow,int Cost){
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
e[tot].flow=Flow;
e[tot].cost=Cost;
}
bool spfa(){
std::queue<int>q;
memset(dis,0x3f,sizeof(dis));
memset(flow,0x3f,sizeof(flow));
memset(vis,0,sizeof(vis));
while(!q.empty())
q.pop();
q.push(S);
vis[S]=1;
dis[S]=0;
Last[T]=0;
while(!q.empty()){
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(e[i].flow&&dis[y]>dis[x]+e[i].cost){
dis[y]=dis[x]+e[i].cost;
Last[y]=x;
last[y]=i;
flow[y]=std::min(flow[x],e[i].flow);
if(!vis[y]){
vis[y]=1;
q.push(y);
}
}
}
}
return Last[T];
}
void mcmf(){
while(spfa()){
int now=T;
maxflow+=flow[now];
mincost+=dis[now]*flow[now];
while(now!=S){
e[last[now]].flow-=flow[T];
e[last[now]^1].flow+=flow[T];
now=Last[now];
}
}
}
main(){
scanf("%d%d%d%d",&n,&m,&S,&T);
for(int i=1;i<=m;++i){
int A,B,C,D;
scanf("%d%d%d%d",&A,&B,&C,&D);
add(A,B,C,D);
add(B,A,0,-D);
}
mcmf();
printf("%d %d",maxflow,mincost);
return 0;
}