【网络流】最大流:算法模板,二分图匹配

算法模板

Ek算法 时间复杂度: O ( n m 2 ) O(nm^2) O(nm2)

给一个流网络,维护残留网络。

while(){
	①在当前的残留网络里找增广路(bfs):f'
	②更新残留网络:把当前的残留网络 Gf 更新为新流的残留网络 G(f+f')
} 

①简单的遍历,用 b f s bfs bfs即可。

②假设当前残留网络里正向边的容量为 c 1 c_1 c1,反向边的容量为 c 2 c_2 c2,且增广路径流了 k k k的流量,正向边的容量变为 c 1 − k c_1-k c1k,反向边的容量变为 c 2 + k c_2+k c2+k

更新:一旦找到一条从 s s s t t t的增广路径之后,首先考虑整个增广路径最多可以增加多少流,即最小边的流量。若最小边的流量为 k k k,那么整条增广路径的流量就为 k k k,再用前面所描述的方式将这条路径上所有的边的容量更新即可。

p r e [ ] pre[] pre[]记录前驱边,即可记录整条路径。

  • E k Ek Ek算法在求最大流时一般用不到,但 E K EK EK算法在求最小费用流时是一个核心算法。

如何存图
用邻接表。为了快速找到每条边的反向边,可以成对存储,即第二条边存第一条边的反向边,第四条边存第三条边的反向边。

原题链接

代码如下:

#include
using namespace std;

const int N=1000+10;
const int M=20000+10;
const int INF=1e9;

int n,m,S,T;
int h[N],ve[M],c[M],ne[M],id;
int q[N],d[N]/*记录当前所有边的容量最小值*/,pre[N];
bool v[N];//判重,避免重复搜索

void add(int x,int y,int z){//维护的是残留网络
    ve[id]=y;c[id]=z;ne[id]=h[x];h[x]=id++;
    ve[id]=x;c[id]=0;ne[id]=h[y];h[y]=id++;
}

bool bfs(){//找增广路
    memset(v,0,sizeof v);
    int he=0,ta=0;
    q[0]=S;v[S]=1;d[S]=INF;
    
    while(he<=ta){
        int x=q[he++];
        
        for(int i=h[x];~i;i=ne[i]){
            int y=ve[i];
            
            if(!v[y]&&c[i]){
                v[y]=1;
                d[y]=min(d[x],c[i]);
                pre[y]=i;//记录点y的前驱边编号
                
                if(y==T)return true;//找到增广路径
                q[++ta]=y;
            }
        }
    }
    return false;
}

int EK(){
    int ans=0;
    while(bfs()){//找到一条增广路径,更新残留网络
        ans+=d[T];
        
        for(int i=T;i!=S;i=ve[pre[i]^1]){
            c[pre[i]]-=d[T];c[pre[i]^1]+=d[T];
        }
    }
    
    return ans;
}

int main(){
    scanf("%d%d%d%d",&n,&m,&S,&T);
    memset(h,-1,sizeof h);
    
    for(int i=1;i<=m;i++){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
    }
    
    printf("%d",EK());
    return 0;
}

Dinic算法 时间复杂度: O ( n 2 m ) O(n^2m) O(n2m)

E K EK EK算法的基础上增加分层图的思想,即每一次不是只增广一条路径,而是增广很多条路。 D i n i c Dinic Dinic算法是对 E K EK EK算法的优化。

找增广路的算法流程:

b f s bfs bfs建分层图。

②通过 d f s dfs dfs把图里可以增广的路径全部搜索出来,统一增广一遍。

维护的是残留网络。

需要使用当前弧优化。即:当前点的某条边满时,跳过这条边,搜索下一条边。

原题链接

代码如下:

#include
using namespace std;

const int N=10000+10;
const int M=200000+10;
const int INF=1e9;

int n,m,S,T;
int h[N],ve[M],c[M],ne[M],id;
int q[N],d[N],cur[N]/*当前弧优化*/;

void add(int x,int y,int z){
    ve[id]=y;c[id]=z;ne[id]=h[x];h[x]=id++;
    ve[id]=x;c[id]=0;ne[id]=h[y];h[y]=id++;
}

bool bfs(){//找增广路,建分层图
    int he=0,ta=0;
    memset(d,-1,sizeof d);
    q[0]=S;d[S]=0;cur[S]=h[S];
    
    while(he<=ta){
        int x=q[he++];
        
        for(int i=h[x];~i;i=ne[i]){
            int y=ve[i];
            
            if(d[y]==-1&&c[i]){
                d[y]=d[x]+1;//建立分层图
                cur[y]=h[y];//当前点的当前弧为第一条边
                if(y==T)return true;
                q[++ta]=y;
            }
        }
    }
    return false;
}

int dfs(int u,int limit){//从点u开始搜索,从s到u最大流量为limit
    if(u==T)return limit;
    int flow=0;//从u到t的最大流量
    
    for(int i=cur[u]/*从当前未满的路径开始搜*/;~i&&flow

二分图匹配

飞行员配对方案问题

原题链接

给出一个二分图,求二分图最多有多少个匹配。要求是每一个点只能在一个匹配里面,同时输出方案。

由数据范围可以知道用匈牙利算法完全没有问题。但本文使用最大流解决这个问题。

  • 先考虑如何建图:

①从 s s s向第一个点集(即外籍飞行员)中的每一个点连一条容量是 1 1 1的边。

②从第二个点集(即英国飞行员)中的每一个点向 t t t连一条容量是 1 1 1的边。

③对于两个点集之间的边,从第一个点集向第二个点集连一条容量是 1 1 1的边。

【网络流】最大流:算法模板,二分图匹配_第1张图片

  • 接下来考虑这样一个网络 G G G的可行流的集合 { f } \{f\} { f}与原问题 P P P的解的集合 { S } \{S\} { S}是否一一对应。

注意: { f } \{f\} { f}并非所有可行流的集合,而是所有整数可行流的集合。

①证明 S ⇒ f S \Rightarrow f Sf,即原问题的可行解为流网络里的可行流。
【网络流】最大流:算法模板,二分图匹配_第2张图片
如图,所连接的边为原图的一个可行解。将所连边的流量都记为 1 1 1

(i)考虑流量守恒:从 s s s流出的流量为 4 4 4,流入 t t t的流量也为 4 4 4,且除 s , t s,t s,t外所有被连接的点流入流出的流量皆为 1 1 1

(ii)考虑容量限制:显然成立。

因此原问题的可行解可以转化为流网络中的一个可行流。

②证明 f ⇒ S f \Rightarrow S fS,即流网络里的可行流为原问题的可行解。

先随意构造出流网络中的一个可行流。

对于第一个点集,每一个点流入的流量一定为从 s s s流入的流量,在构造流网络时,第一个点集中的每一个点只有一条边与 s s s相连,即第一个点集中的每一个点最多的流入的流量为 1 1 1。根据流量守恒,从第一个点集中的每一个点流出的流量也为 1 1 1,即第一个点集中的任意一个点只能与第二个点集中的某一个点相匹配。

对于第二个点集,每一个点流出的流量一定为流入 t t t的流量,在构造流网络时,第二个点集中的每一个点只有一条边与 t t t相连,即第二个点集中的每一个点最多流出的流量为 1 1 1。根据流量守恒,向第二个点集中的每一个点流入的流量也为 1 1 1,即第二个点集中的任意一个点只能与第一个点集中的某一个点相匹配。

综上,第一个点集与第二个点集相匹配的每一组点中的任意一点都不会再与其他点匹配。满足原问题中的可行解的要求。

因此流网络中的一个可行流可以转化为原问题中的可行解。

因此所构造出来的流网络的最大流即为原问题中的最大匹配。

时间复杂度:若使用匈牙利算法,为 O ( n m ) O(nm) O(nm);若使用 D i n i c Dinic Dinic算法,为 O ( m n ) O(m\sqrt{n}) O(mn )

如何找方案:即为映射过程。枚举中间的所有边,看哪条边的流量是满的,流量为满的边即为匹配。

代码如下:

#include
using namespace std;

const int N=100+10;
const int M=6000+10;
const int INF=1e9;

int n,m,S,T;
int h[N],ve[M],c[M],ne[M],id;
int q[N],d[N],cur[N];

void add(int x,int y,int z){
    ve[id]=y;c[id]=z;ne[id]=h[x];h[x]=id++;
    ve[id]=x;c[id]=0;ne[id]=h[y];h[y]=id++;
}

bool bfs(){
    int he=0,ta=0;
    memset(d,-1,sizeof d);
    q[0]=S;d[S]=0;cur[S]=h[S];
    
    while(he<=ta){
        int x=q[he++];
        
        for(int i=h[x];~i;i=ne[i]){
            int y=ve[i];
            
            if(d[y]==-1&&c[i]){
                d[y]=d[x]+1;
                cur[y]=h[y];
                if(y==T)return true;
                q[++ta]=y;
            }
        }
    }
    return false;
}

int dfs(int u,int limit){
    if(u==T)return limit;
    
    int flow=0;
    for(int i=h[u];~i&&flowm&&ve[i]<=n&&!c[i])//判断当前边是否在中间的位置,并且边为满流
            printf("%d %d\n",ve[i^1],ve[i]);
    
    return 0;
}

圆桌问题

m m m个单位可看做有 m m m个点的点集, n n n张圆桌可看做有 n n n个点的点集,很明显是一个二分图匹配问题,但与一般的二分图匹配问题不同的是每个点可以选多次。因此此题为二分图多重匹配问题

建图:与上图建图相似。

①从 s s s向第一个点集(即单位)中的每一个点连一条容量为对应单位的代表数的边。

②从第二个点集(即圆桌)中的每一个点向 t t t连一条容量为对应圆桌的容量的边。

③对于两个点集之间的边,从第一个点集向第二个点集连一条容量是 1 1 1的边。

【网络流】最大流:算法模板,二分图匹配_第3张图片

  • 接下来考虑这样一个网络 G G G的可行流的集合 { f } \{f\} { f}与原问题 P P P的解的集合 { S } \{S\} { S}是否一一对应。

①证明 S ⇒ f S \Rightarrow f Sf,即原问题的可行解为流网络里的可行流。

先随意选择一些中间的边:
【网络流】最大流:算法模板,二分图匹配_第4张图片
将所有选择的边的流量赋予 1 1 1

s s s到第一个点集:每个单位派几个人过去,流量就为几。

从第二个点集到 t t t:每张圆桌坐几个人,流量就为几。

检验是否为流网络的可行流。

【网络流】最大流:算法模板,二分图匹配_第5张图片
检验这个流是否为可行流,即检验可行流的两个性质是否成立。

(i)容量限制

中间所有边的流量最大是 1 1 1,容量都为 1 1 1,满足。

对于左边的每条边:因为方案是合法的,因此每个单位派出去的代表个数(即流量)一定不会超过每个单位的代表数(即容量)。

同理,右边的边也满足容量限制。

(ii)流量守恒

同理,因为方案合法,流量守恒也是满足的。

因此原问题的任意一个可行解都可以构造出流网络中某一个可行流。

②证明 f ⇒ S f \Rightarrow S fS,即流网络里的可行流为原文体中的可行解。

因为中间的边的容量为 1 1 1,因此每个单位最多向每个圆桌派一名代表。符合题意。

因为左边的边的容量为某单位的最大人数,所以流量不可能超过总人数(即容量)。

同理,右边的边也符合题意。

因此流网络中的任意一个可行流都对应原问题中的某一个可行解。

  • 求某一合法方案,使得所有代表都有圆桌坐。

因此所有从 s s s出发的边都为满流,原问题即求原网络的最大流,且验证最大流是否等于总人数。若等于总人数,则为一个合法方案;若不等于则原问题无解。

所以可以将原问题转化为一个最大流问题

输出方案:看中间的边哪一些为满流。

代码如下:

using namespace std;

const int N=500+10;
const int M=1000000+10;
const int INF=1e9;

int n,m,S,T;
int h[N],ve[M],c[M],ne[M],id;
int q[N],d[N],cur[N];

void add(int x,int y,int z){
    ve[id]=y;c[id]=z;ne[id]=h[x];h[x]=id++;
    ve[id]=x;c[id]=0;ne[id]=h[y];h[y]=id++;
}

bool bfs(){
    int he=0,ta=0;
    memset(d,-1,sizeof d);
    q[0]=S;d[S]=0;cur[S]=h[S];
    
    while(he<=ta){
        int x=q[he++];
        
        for(int i=h[x];~i;i=ne[i]){
            int y=ve[i];
            
            if(d[y]==-1&&c[i]){
                d[y]=d[x]+1;
                cur[y]=h[y];
                if(y==T)return true;
                q[++ta]=y;
            }
        }
    }
    return false;
}

int dfs(int u,int limit){
    if(u==T)return limit;
    int flow=0;
    
    for(int i=cur[u];~i&&flowm&&ve[j]<=m+n&&!c[j])
                    printf("%d ",ve[j]-m);
            
            puts("");        
        }
    }
    
    return 0;
}

你可能感兴趣的:(算法进阶课,图论)