zoukankan      html  css  js  c++  java
  • 网络流学习笔记

    1 - 最大流

    问题概述

    基本概念参见 OI-Wiki

    有一张图,要求从源点流向汇点的最大流量(可以有很多条路到达汇点)。

    高科技:O(nm)最大流

    Edmond-Karp(EK)

    复杂度:(mathcal{O}(nm^2)) ,处理范围在 (1e3sim 1e4) 左右。

    思路:从源点到汇点有很多条路径,每次抽取一条,并找出路径上的边权最小值 (x) ,然后把路径上所有边权都减去(x)(也就是找增广路,这条路径上流过 (x) 的流量),直到没有路径可以给汇点增加流量为止。显然, (0) 容量是没有用的。具体实现采用 BFS 。

    如果是单纯的建边跑不了最大流,因为如果之前选的并不是最优路径后面就不一定能取到最大值。EK 支持一个 反悔操作 ,也就是建反向边,初始为 (0) ,如果对应的正向边剩余容量减少,那么相应的反向边容量就增加,之后反向边就能流动了。

    //Author:RingweEH
    //P3376 【模板】网络最大流
    const int N=210,M=5e3+10;
    const ll INF=0x7f7f7f7f;
    struct Edge
    {
    	int to,nxt; ll val;
    }e[M<<1];
    int n,m,S,T,tot=1,head[N],pre[N],mp[N][N];
    ll mxflow,dis[N];
    bool vis[N];
    
    void Add( int u,int v,int ll w )
    {
    	e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].val=w;
    	e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; e[tot].val=0;	//反向边
    }
    
    bool BFS()
    {
    	for ( int i=1; i<=n; i++ )
    		vis[i]=0;
    	queue<int> q; q.push( S ); vis[S]=1; dis[S]=INF;
    	while ( !q.empty() )
    	{
    		int u=q.front(); q.pop();
    		for ( int i=head[u]; i; i=e[i].nxt )
    		{
    			if ( e[i].val==0 ) continue;	//流没了
    			int v=e[i].to;
    			if ( vis[v] ) continue; 
    			dis[v]=min( dis[u],e[i].val );	//取路径上的最小剩余容量
    			pre[v]=i; 		//记录从哪条边过来
    			q.push( v ); vis[v]=1;
    			if ( v==T ) return 1;	//走到汇点了
    		}
    	}
    	return 0;
    }
    
    void Update()		//每次找到一条增广路,并更新路径上的值
    {
    	int u=T;
    	while ( u^S )
    	{
    		int v=pre[u];
    		e[v].val-=dis[T]; e[v^1].val+=dis[T];		
    		//这条增广路的流量,用来更新路径上所有边和反向边
    		u=e[v^1].to;		//本来记录的是过来的边,回去要反向
    	}
    	mxflow+=dis[T];		//总流量增加
    }
    
    int main()
    {
    	n=read(); m=read(); S=read(); T=read();
    	for ( int i=1; i<=m; i++ )
    	{
    		int u,v; ll w; u=read(); v=read(); w=read();
    		if ( mp[u][v]==0 ) Add( u,v,w ),mp[u][v]=tot;
    		else e[mp[u][v]-1].val+=w;
    		//重边容量合并
    	}
    
    	while ( BFS() ) Update();
    
    	printf( "%lld
    ",mxflow ); 
    
    	return 0;
    }
    

    Dinic

    复杂度 (mathcal{O}(nm^2)) ,处理范围在 (1e4sim 1e5) 左右,稠密图较明显,求解二分图最大匹配是 (mathcal{O}(msqrt n)) .

    思路:BFS 得到分层图,(d[x]) 表示层次(即 (S)(x) 的最少边数)。残量网络中,满足 (d[y]=d[x]+1) 的边 ((x,y)) 构成的子图称为分层图。显然,这一定是一张有向无环图,从 (S) 开始 DFS ,每次向下找一个点,直到到达 (T) ,然后回溯回去,找另外的点搜索出多条增广路。

    总结:

    • 在残量网络上 BFS 求出节点层次,构造分层图
    • 在分层图上 DFS 找增广路,回溯的同时更新边权

    当前弧优化 :(没加这个复杂度是假的)

    对于一个节点 (u) ,在 DFS 中, for ( int i=head[u]; i; i=e[i].nxt ) 走到了第 (i) 条弧时,前 (i-1) 条到汇点 (T) 的路径一定流满了,再访问 (u) 时前 (i-1) 条就没有意义了。所以每次枚举 (u) 的时候,要改变枚举的起点,即 (nw)for ( int i=now[x]; i; i=e[i].nxt ) .

    //Author:RingweEH
    //P3376 【模板】网络最大流
    const int N=210,M=5e3+10;
    const ll INF=0x7f7f7f7f;
    struct Edge
    {
    	int to,nxt; ll val;
    }e[M<<1];
    int n,m,S,T,tot=1,nw[N],head[N];
    ll mxflow,dis[N];
    
    void Add( int u,int v,ll w )
    {
    	e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].val=w;
    	e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; e[tot].val=0;
    }
    
    bool BFS()		//构建分层图,判断残量网络还能不能到达汇点
    {
    	for ( int i=1; i<=n; i++ )
    		dis[i]=INF;
    	queue<int> q; q.push( S ); dis[S]=0; nw[S]=head[S];
    	while ( !q.empty() )
    	{
    		int u=q.front(); q.pop();
    		for ( int i=head[u]; i; i=e[i].nxt )
    		{
    			int v=e[i].to;
    			if ( e[i].val>0 && dis[v]==INF )
    			{
    				q.push( v ); nw[v]=head[v]; dis[v]=dis[u]+1;
    				if ( v==T ) return 1;
    			}
    		}
    	}
    	return 0;
    }
    
    int DFS( int u,ll sum )		//sum记录增广路上的最小容量
    {
    	if ( u==T ) return sum;
    	ll res=0;
    	for ( int i=nw[u]; i && sum; i=e[i].nxt )
    	{
    		nw[u]=i; int v=e[i].to;
    		if ( e[i].val>0 && (dis[v]==dis[u]+1) )
    		{
    			ll tmp=DFS( v,min(sum,e[i].val) );
    			if ( tmp==0 ) dis[v]=INF;	//v不能再增广了,剪枝
    			e[i].val-=tmp; e[i^1].val+=tmp;
    			res+=tmp; sum-=tmp;		//res是经过该点的流量和,sum是经过该点剩余流量
    		}
    	}
    	return res;
    }
    
    int main()
    {
    	n=read(); m=read(); S=read(); T=read();
    	for ( int i=1; i<=m; i++ )
    	{
    		int u=read(),v=read(); ll w=read();
    		Add( u,v,w );
    	}
    
    	while ( BFS() ) mxflow+=DFS(S,INF);
    
    	printf( "%lld
    ",mxflow );
    
    	return 0;
    }
    

    ISAP

    复杂度:(mathcal{O}(n^2m))

    看看 Dinic 的 while ( BFS() ) mxflow+=DFS(S,INF); ,这样重复 BFS 显然效率低下。于是出现了 ISAP ,只需要跑一遍 (bfs)

    大体流程:

    • (T)(S) 跑 BFS
    • (S)(T) 跑 DFS
    • 重复第二步的 DFS 直到出现断层

    ISAP 只跑一遍 BFS 标记层次,然后每个点的层次随着 DFS 变高。记 (gap[i]) 表示高度为 (i) 的点的个数,(d[i]) 表示 (i) 的层次,当结束 (i) 的增广之后,遍历残量网络上 (i) 的所有出边,找到 (d) 最小的出点 (j) ,令 (d[i]=d[j]+1) ,如果没有出边,(d[i]=n) 。当 (gap[i]=0) 时出现断层,(S)(T) 不连通,就可以停止了(这其实是 GAP 优化)。

    和 Dinic 类似,ISAP 同样有当前弧优化。且最短路的修改具有连续性,每次不需要真的遍历残量网络出边,直接给 (d[i]) 加一即可。

    //Author:RingweEH
    const int N=210,M=5e3+10;
    const ll INF=0x7f7f7f7f;
    struct Edge
    {
        int to,nxt; ll val;
    }e[M<<1];
    int n,m,S,T,tot=1,head[N],dep[N],gap[N],nw[N];
    ll mxflow=0;
    
    void Add( int u,int v,ll w )
    {
        e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].val=w;
        e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; e[tot].val=0;
    }
    
    void BFS()
    {
        memset( dep,-1,sizeof(dep) ); memset( gap,0,sizeof(gap) );
        dep[T]=0; gap[0]=1;
        queue<int> q; q.push( T );
        while ( !q.empty() )
        {
            int u=q.front(); q.pop();
            for ( int i=head[u]; i; i=e[i].nxt )
            {
                int v=e[i].to;
                if ( dep[v]>-1 ) continue;
                q.push( v ); dep[v]=dep[u]+1; gap[dep[v]]++;
            }
        }
    }
    
    int DFS( int u,ll sum )
    {
        if ( u==T ) { mxflow+=sum; return sum; }
        ll res=0;
        for ( int i=nw[u]; i; i=e[i].nxt )
        {
            nw[u]=i; int v=e[i].to;
            if ( e[i].val && dep[v]+1==dep[u] )
            {
                int tmp=DFS( v,min(e[i].val,sum) );
                if ( tmp )
                {
                    e[i].val-=tmp; e[i^1].val+=tmp;
                    sum-=tmp; res+=tmp;
                }
                if ( sum==0 ) return res;
            }
        }
        gap[dep[u]]--; 
        if ( gap[dep[u]]==0 ) dep[S]=n+1;
        dep[u]++; gap[dep[u]]++;
        return res;
    }
    
    int main()
    {
        n=read(); m=read(); S=read(); T=read();
        for ( int i=1; i<=m; i++ )
        {
            int u=read(),v=read(); ll w=read();
            Add( u,v,w );
        }
    
        BFS(); mxflow=0;
        while ( dep[S]<n ) 
        {
            memcpy( nw,head,sizeof(head) );  DFS( S,INF );
        }
    
        printf( "%lld
    ",mxflow );
    
        return 0;
    }
    

    HLPP(最高标号预流推进)

    推荐博客

    前面的三种算法都是基于 增广路 的思想。

    而这里是另一类:预流推进

    预流推进思想:允许水流在非源汇的节点中暂时保存(称为 超额流 ),同时伺机将超额流 推送 出去。只要最后所有的节点超额流为 (0) ,那么就是合法的。为了避免死循环,引入每个节点的 高度 ,只允许高度较高的节点向高度较低的推送。如果一个节点因为高度原因不能推送出去,那么就抬高这个高度,这个操作称为 重贴标签

    HLPP算法流程:用 (e[v]) 表示超额流,(h[v]) 表示高度。

    首先,将所有除了源点以外的节点高度设为 (0) ,源点 (S) 设为 (n) ,并将 (S) 的所有边都充满流量推送出去(放水)。将所有推送后超额流不为 (0) 的节点加入以高度为关键字的优先队列中等待推送。

    然后,每次取出对首 (u) 并尝试推送:

    • 逐一检查 (u) 的出边,如果某条边还有流量,并且另一端 (v) 满足 (h[v]+1=h[u]) ,那么这条边可以推送。
    • 推送流量不能超过边的容量,也不能超过 (e[u])
    • 推送完毕后修改超额流,如果 (v) 不在队列中那么加入

    完成之后,如果 (e[u]>0) ,那么说明需要对 (u) 重贴标签。找到有流量并且另一端 (h[v]) 最小的边,令 (h[u]=h[v]+1) ,把操作完的 (u) 加入优先队列。

    直到队列为空。

    虽然这样复杂度正确,为 (mathcal{O}(n^2sqrt m)) ,但是需要一些优化才能在随机数据下和增广路算法相当。

    1. 可以通过一边 BFS 将每个点的初始高度设为到汇点的距离。(除了 (S)
    2. 如果一个点被重贴标签之后,原来的高度已经没有点了,那么高于它的点必然不能推到 (T) ,所以可以将高度大于 (h[u]) 并且小于 (n+1)(h) 设为 (n+1) ,以便尽快推给 (S) 。也就是和 ISAP 一样的 GAP 优化。

    注意 gap 大小要开两倍。


    然而事实上博主的写法并不适合我这种大常数选手。于是跑到 LOJ 上去学习了一番最优解,得到了如下写法。

    这写法长这样:

    记录高度为 (i) 的节点个数,具体节点,高度为 (i) 的有超额流的节点,记录更改高度的总次数,记录最大高度;

    每次重贴标签直接从 (T) 开始重新对着残量网络 BFS;

    一边往外流一边记录所有出边的端点高度最小值(为了有剩余超额流的时候直接更新高度);

    断层就直接把所有高度比当前高的节点置为 INF ;

    初始化源点无限流量,先重贴一遍,然后按照高度从高到低依次往外推流,如果更改高度的总次数大于某个阀值,那么就再重贴一遍。

    具体可以看代码QWQ 跑得飞快。

    //Author:RingweEH
    #define pb push_back
    const int N=1203,INF=0x3f3f3f3f;
    struct Edge
    {
        int to,nxt,val;
        Edge ( int _to,int _nxt,int _val ) : to(_to),nxt(_nxt),val(_val) {}
    };
    vector<Edge> g[N];
    int n,m,S,T,h[N],cnt[N],work,ht,rest[N];
    vector<int> las[N],gap[N];
    
    void Add( int u,int v,int w )
    {
        g[u].pb( Edge(v,g[v].size(),w) );
        g[v].pb( Edge(u,g[u].size()-1,0) );
    }
    
    void Update_height( int u,int nw )
    {
        ++work;         //work记录更改高度的次数
        if ( h[u]^INF ) --cnt[h[u]];
        h[u]=nw; 
        if ( nw==INF ) return;
        ++cnt[nw]; ht=nw;    //cnt[i]记录高度为i的节点个数
        gap[nw].pb(u);          //gap[i]记录高度为i的节点
        if ( rest[u] ) las[nw].pb(u);   //las[i]记录高度为i且还有超额流的节点
    }
    
    void Relabel()      //重置高度
    {
        work=0; memset( h,0x3f,sizeof(h) ); memset( cnt,0,sizeof(cnt) );
        for ( int i=0; i<=ht; i++ )
            las[i].clear(),gap[i].clear();
        h[T]=0; queue<int> q; q.push(T);
        while ( !q.empty() )        //每次重新BFS
        {
            int u=q.front(); q.pop();
            for ( Edge &e : g[u] )
                if ( h[e.to]==INF && g[e.to][e.nxt].val ) //如果还有流量且另一个端点没被更新过
                {
                    q.push( e.to ); Update_height( e.to,h[u]+1 );   //设为到汇点的距离
                }
            ht=h[u];        //记录最大的一个高度
        }
    }
    
    void Push( int u,Edge &e )      //u的超额流通过边e往外流
    {
        if ( rest[e.to]==0 ) las[h[e.to]].pb(e.to);     //如果没在里面,现在就要在了
        int del=min( rest[u],e.val );   
        e.val-=del; g[e.to][e.nxt].val+=del;
        rest[u]-=del; rest[e.to]+=del;
    }
    
    void Push_Flow( int u )
    {
        int nh=INF;
        for ( Edge &e : g[u] )   
            if ( e.val )
                if ( h[u]==h[e.to]+1 )
                {
                    Push( u,e );
                    if ( rest[u]<=0 ) return;
                }
                else nh=min( nh,h[e.to]+1 );        
                //一边往外流一边以防万一要更新,收集最小值
        if ( cnt[h[u]]>1 ) Update_height( u,nh );       //没断层,直接更新
        else
            for ( int i=h[u]; i<N; i++ )        //断层了
            {
                for ( int j : gap[i] ) 
                    Update_height( j,INF );
                gap[i].clear();
            }
    }
    
    int HLPP()
    {
        memset( rest,0,sizeof(rest) );
        rest[S]=2147483647;
        Relabel();
        for ( Edge &e : g[S] )      //从源点往外推
            Push( S,e );
        for ( ; ~ht; --ht )
            while ( !las[ht].empty() )      //枚举高度,从高到低依次往外推
            {
                int u=las[ht].back(); las[ht].pop_back();
                Push_Flow( u );
                if ( work>20000 ) Relabel();        //如果改变得过多了那么就重新标记
            }
        return rest[T];
    }
    
    int main()
    {
        n=read(); m=read(); S=read(); T=read();
        for ( int i=1,u,v,w; i<=m; i++ )
            u=read(),v=read(),w=read(),Add( u,v,w );
        printf( "%d
    ",HLPP() );
    }
    

    2 - 最小割

    可以参考这篇论文

    概念&定理

    对于一个网络流图 (G=(V,E)) ,将所有点划分成 (S)(T=V-S) ,其中源点 (sin S) ,汇点 (tin T) . 定义割 ((S,T)) 的容量为所有 (S)(T) 的边的容量之和。

    最大流最小割定理 :最大流=最小割,即 (f(s,t)_{max}=c(S,T)_min)证明

    求方案:可以从源点 (s) 开始 DFS ,每次走残量大于 (0) 的边,找到所有 (S) 点集内的点。

    求割边数量:将容量变为 (1) ,跑 Dinic 即可。

    模型

    有两个集合 (A,B)(n) 个物品,第 (i) 个物品放入 (A) 产生花费 (a_i) ,放入 (B)(b_i) ,还有若干个限制 ((u_i,v_i,w_i)) ,如果 (u_i)(v_i) 不在同一个集合里花费 (w_i) ,每个物品只能属于一个集合,求最小代价。

    二者选其一 的最小割。对每个集合设置源点 (s) 和汇点 (t) ,第 (i) 个点由 (s) 连一条容量为 (a_i) 的边,向 (t) 连一条容量为 (b_i) 的边,在 (u_i,v_i) 之间连容量为 (w_i) 的双向边。当源汇不相连的时候,代表这些点都选择了其中一个集合。如果将连向 (s) 或者 (t) 的边割开,表示不放在 (A) 或者 (B) ,如果物品间割开,表示不放在同一个集合。最小割就是最小花费。

    3 - 费用流

    每条边除了容量限制还有单位流量的费用 (w(u,v)) 。当 ((u,v)) 的流量为 (f(u,v)) 时,花费 (f(u,v) imes w(u,v)) .

    研究问题:最小费用最大流

    SSP

    SSP(Successive Shortest Path)算法:在最大流的 EK 算法求解最大流的基础上,把 用 BFS 求解任意增广路 改为 用 SPFA 求解单位费用之和最小的增广路 即可。

    相当于 把费用作为边权,在残存网络上求最短路

    //Author:RingweEH
    const int N=410,M=15010,INF=0x3f3f3f3f;
    struct Edge
    {
    	int to,nxt,flow,cap;
    	Edge ( int _to=0,int _nxt=0,int _flow=0,int _cap=0 ) :
    	 	to(_to),nxt(_nxt),flow(_flow),cap(_cap) {}
    }e[M<<1];
    int n,m,S,T,tot=1,head[N];
    int pre[N],dis[N],mxflow,mncost;
    bool vis[N];
    
    void Add( int u,int v,int fl,int cap )
    {
    	e[++tot]=Edge(v,head[u],fl,cap); head[u]=tot;
    	e[++tot]=Edge(u,head[v],0,-cap); head[v]=tot;
    }
    
    bool BFS()
    {
    	for ( int i=1; i<=n; i++ )
    		vis[i]=0,dis[i]=INF;
    	queue<int> q; q.push( S ); dis[S]=0;
    	while ( !q.empty() )
    	{
    		int u=q.front(); q.pop(); vis[u]=0;
    		for ( int i=head[u]; i; i=e[i].nxt )
    		{
    			int v=e[i].to; 
    			if ( dis[v]>dis[u]+e[i].cap && e[i].flow )
    			{
    				dis[v]=dis[u]+e[i].cap; pre[v]=i;
    				if ( !vis[v] ) vis[v]=1,q.push( v );
    			}
    		}
    	}
    	return dis[T]!=INF;
    }
    
    void Update()
    {
    	int mn=INF;
    	for ( int i=T; i^S; i=e[pre[i]^1].to )
    		bmin( mn,e[pre[i]].flow );
    	for ( int i=T; i^S; i=e[pre[i]^1].to )
    	{
    		e[pre[i]].flow-=mn; e[pre[i]^1].flow+=mn;
    		mncost+=mn*e[pre[i]].cap;
    	}
    	mxflow+=mn;
    }
    
    int main()
    {
    	n=read(); m=read(); S=1; T=n;
    	for ( int i=1,u,v,f,c; i<=m; i++ )
    		u=read(),v=read(),f=read(),c=read(),Add( u,v,f,c );
    
    	while ( BFS() ) Update();
    
    	printf( "%d %d
    ",mxflow,mncost );
    }
    

    类 Dinic

    在 Dinic 算法的基础上进行改进,把 BFS 求分层图 改为用 SPFA求一条单位费用之和最小的路径(由于有负权的反向边,不能直接用 Dijkstra ),相当于 (w(u,v)) 当做边权在残量网络上求最短路

    优化:使用 Primal-Dual 原始对偶算法,将 SPFA 改成 Dijkstra (有需要看 这篇 ,不过 代码也不长?

    复杂度上界:(mathcal{O}(nmf))

    //Author:RingweEH
    const int N=410,M=15010,INF=0x3f3f3f3f;
    struct Edge
    {
    	int to,nxt,flow,cap;
    	Edge ( int _to=0,int _nxt=0,int _flow=0,int _cap=0 ) :
    	 	to(_to),nxt(_nxt),flow(_flow),cap(_cap) {}
    }e[M<<1];
    int n,m,S,T,tot=1,head[N],dis[N],mxflow,mncost,nw[N];
    bool vis[N];
     
    void Add( int u,int v,int fl,int cap )
    {
    	e[++tot]=Edge(v,head[u],fl,cap); head[u]=tot;
    	e[++tot]=Edge(u,head[v],0,-cap); head[v]=tot;
    }
     
    bool BFS()	
    {
    	for ( int i=1; i<=n; i++ )
    		dis[i]=INF,vis[i]=0;
    	queue<int> q; q.push( S ); dis[S]=0; nw[S]=head[S]; vis[S]=1;
    	while ( !q.empty() )
    	{
    		int u=q.front(); q.pop(); vis[u]=0;
    		for ( int i=head[u]; i; i=e[i].nxt )
    		{
    			int v=e[i].to;
    			if ( e[i].flow && dis[v]>dis[u]+e[i].cap )
    			{
    				nw[v]=head[v]; dis[v]=dis[u]+e[i].cap;
    				if ( !vis[v] ) vis[v]=1,q.push(v);
    			}
    		}
    	}
    	return dis[T]!=INF;
    }
     
    int DFS( int u,int sum )		
    {
    	if ( u==T ) return sum;
    	vis[u]=1; int res=0;
    	for ( int i=nw[u]; i && sum; i=e[i].nxt )
    	{
    		nw[u]=i; int v=e[i].to;
    		if ( !vis[v] && e[i].flow && (dis[v]==dis[u]+e[i].cap) )
    		{
    			int tmp=DFS( v,min(sum,e[i].flow) );
    			if ( tmp )
    			{
    				e[i].flow-=tmp; e[i^1].flow+=tmp;
    				res+=tmp; sum-=tmp;	mncost+=e[i].cap*tmp;
    			}
    		}
    	}
    	vis[u]=0; return res;
    }
     
    int main()
    {
    	n=read(); m=read(); S=1; T=n;
    	for ( int i=1,u,v,f,c; i<=m; i++ )
    		u=read(),v=read(),f=read(),c=read(),Add(u,v,f,c);
     
    	while ( BFS() ) mxflow+=DFS(S,INF);
     
    	printf( "%d %d
    ",mxflow,mncost );
     
    	return 0;
    }
    

    4 - 上下界网络流

    解决每条边流量有上下界限制的网络流。

    无源汇上下界可行流

    问题 :有 (n) 个点, (m) 条边,每条边有一个流量下界 (low) 和流量上界 (up) ,求可行方案使得所有点满足流量平衡的前提下,所有边满足流量限制。

    思路 :两个限制同时考虑会很麻烦,先考虑下界。

    先给每条弧加一个 (low) 的流量下界,那么这个限制就没了。但是现在这个图不一定满足流量平衡,称为初始流

    应该在这张图上再添加一个附加流,使得初始流和附加流之和满足流量平衡。显然,每条弧的附加流上界是 (up-low) ,所以不妨在建边的时候直接设容量为 (up-low) ,提出 (low) . 设现在图中点的流入流出量之差为 (c[i])

    设置虚拟源汇,源点向每个 (c[i]>0) 的点连边,容量为 (c[i]) ;每个 (c[i]<0) 的点向汇点连边,容量为 (-c[i]) . 如果某个点 (x)(c[x]>0) ,那么就从源点给它 (c[x]) 的流,这些流在原本的边上流动,直到流到某个 (c[y]<0) 的点 (y) ,通过这里流出。之后留在原有边上的流量就是附加流。

    如果附加流加上初始流之后流量守恒,那么直接与虚拟源点和汇点相连的边应该都满流。由于 (c[]) 之和为 (0) ,所以新图中的最大流要等于 (sum [c[i]>0]c[i]) . 如果成立那么就存在可行流。求流量就直接找每条边的反向弧即可。

    模板

    //Author:RingweEH
    int main()
    {
        n=read(); m=read(); S=n+1; T=S+1;
        for ( int i=1; i<=m; i++ )
        {
            int u=read(),v=read(),lower=read(),upper=read();
            Add( u,v,upper-lower ); c[v]+=lower; c[u]-=lower;
            low[i]=lower;
        }
    
        int Sum=0;
        for ( int i=1; i<=n; i++ )
        {
            if ( c[i]>0 ) Add( S,i,c[i] ),Sum+=c[i];
            if ( c[i]<0 ) Add( i,T,-c[i] );
        }
     
        while ( BFS() ) mxflow+=DFS(S,INF);
    
        if ( mxflow==Sum )
        {
            printf( "YES
    " );
            for ( int i=3,j=1; j<=m; i+=2,j++ )
                printf( "%lld
    ",e[i].flow+low[j] );
        }
        else printf( "NO
    " );
     
        return 0;
    }
    

    有源汇上下界可行流

    源点和汇点流量不守恒,但是可以手动守恒:从 (T)(S) 连一条下界为 (0) ,上界为 INF 的边,跑无源汇即可。流量就是 (T)(S) 的反向弧流量。

    有源汇上下界最大流

    在有源汇上下界可行流的基础上,在残量网络上跑最大流,相加即可。显然这样不会超出上下界。

    具体实现:用虚拟源汇跑完之后,再用实际的源汇跑跑一遍最大流即可。

    原理:原图中跑完第一次,剩下的就是残量网络,且可行流保存在 (T)(S) 的反向边中。这时候跑 (S)(T) 的最大流,就得到了残量网络最大流+将 (ST) 反向边退回去的流量,也就是最大流加上可行流。

    模板

    //Author:RingweEH
    int main()
    {
        n=read(); m=read(); int s=read(),t=read(); S=n+1,T=S+1;
        for ( int i=1; i<=m; i++ )
        {
            int u=read(),v=read(),lower=read(),upper=read(); low[i]=lower;
            Add( u,v,upper-lower ); c[v]+=lower; c[u]-=lower;
        }
    
        int Sum=0; Add( t,s,INF );
        for ( int i=1; i<=n; i++ )
        {
            if ( c[i]>0 ) Add( S,i,c[i] ),Sum+=c[i];
            if ( c[i]<0 ) Add( i,T,-c[i] );
        }
        while ( BFS() ) mxflow+=DFS(S,INF);
        if ( mxflow!=Sum ) { printf( "please go home to sleep" ); return 0; }
        S=s; T=t; mxflow=0;
        while ( BFS() ) mxflow+=DFS(S,INF);
        printf( "%d
    ",mxflow );
     
        return 0;
    }
    

    有源汇上下界最小流

    和最大流一样,先求出可行流,然后考虑在此基础上减去最大的流量,使之仍然守恒。

    如果我们找到一条 (T o S) 的增广路,那么就说明去掉这条增广路上的流量,仍然守恒。那么就直接从 (T)(S) 跑一遍最大流,用可行流减去流量即可。注意这里 不能 保留 (T)(S) 的 INF 边。

    模板

    //Author:RingweEH
    int main()
    {
        n=read(); m=read(); int s=read(),t=read(); S=n+1,T=S+1;
        for ( int i=1; i<=m; i++ )
        {
            int u=read(),v=read(); ll lower=read(),upper=read(); 
            low[i]=lower; Add( u,v,upper-lower ); c[v]+=lower; c[u]-=lower;
        }
    
        int Sum=0;
        for ( int i=1; i<=n; i++ )
        {
            if ( c[i]>0 ) Add( S,i,c[i] ),Sum+=c[i];
            if ( c[i]<0 ) Add( i,T,-c[i] );
        }
        Add( t,s,INF );
        while ( BFS() ) mxflow+=DFS(S,INF);
        if ( mxflow!=Sum ) { printf( "please go home to sleep" ); return 0; }
        mxflow=e[tot].flow;
        S=t; T=s; e[tot].flow=e[tot^1].flow=0;
        while ( BFS() ) mxflow-=DFS(S,INF);
        printf( "%lld
    ",mxflow );
    
        return 0;
    }
    
  • 相关阅读:
    LeetCode 189. Rotate Array
    LeetCode 965. Univalued Binary Tree
    LeetCode 111. Minimum Depth of Binary Tree
    LeetCode 104. Maximum Depth of Binary Tree
    Windows下MySQL的安装与配置
    LeetCode 58. Length of Last Word
    LeetCode 41. First Missing Positive
    LeetCode 283. Move Zeroes
    《蚂蚁金服11.11:支付宝和蚂蚁花呗的技术架构及实践》读后感
    删除docker下的镜像
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/Network_Flow.html
Copyright © 2011-2022 走看看