zoukankan      html  css  js  c++  java
  • 【网络流】费用流(基于Capacity Scaling)

    目录

    简介
    原理
    代码
    引用资料

    简介

    费用流问题就是要求在所有最大流之中,找到费用最大/最小的问题。
    下面重点讨论最小费用最大流

    原理

    先给出大概的做法:
    在残留网络上沿着最短路(边权即费用)增广,直到得到最大流(无法再增广),那么,假如图中没有负圈,这样的最大流的费用是最小的。

    下面证明正确性:
    假如存在具有相同流量(f) 费用更小的流 (f') ,考察 (f'-f) ,可知它由若干个圈组成,因为 (f'-f) 的费用为负,故在这些圈中至少存在一个负圈

    也就是说,(f) 是最小费用流 (Leftrightarrow) 残留网络中不存在负圈

    基于上面的结论,可以利用归纳法证明上述做法的正确性。

    1. 对于流量为 (0) 的流 (f_0) ,残留网络即为原图,故只要不存在负圈,那么 (f_0) 就是最小费用流。
    2. 下证:若流量为 (i) 的流 (f_i) 为最小费用流,则其通过 (s)(t) 的最短路增广得到的 (f_{i+1}) 是流量为 (i+1) 的最小费用流。
      假设存在 (f_{i+1}') 满足 (f_{i+1}' < f_{i+1}) ,而 (f_{i+1}-f_i) 对应的路径又是 (s ightarrow t) 的最短路,所以 (f_{i+1}'-f_i) 对应的路径一定存在负圈,与 (f_i) 为最小费用流矛盾。

    那么,如果图中存在负圈,如何解决呢?

    此时我们需要对问题进行转化,从 (t ightarrow s) 连一条容量(∞)费用(-∞) 的虚边得到无源汇流。注意到如果存在增广路,则一定可以通过这条路径以及虚边使得费用更小,反之,如果不存在增广路,则在原图相应地得到了最小费用最大流。

    下面只需求无源汇流的最小费用。

    我们采取 capacity scaling 解决这个问题:

    它利用了流本身的性质,实现减少增广的次数的目的。

    这个性质是:将原图中每条边的容量乘二后,最小费用流每条边的流量分别乘二。
    因此,在求出 (lfloor frac{c(u,v)}{2^k} floor) 为边容量的最小费用流 (f) 后,将 (f) 对应的边容量乘二,然后对每条二进制中相应位为 (1) 的边进行容量加一即可得到 (lfloor frac{c(u,v)}{2^{k-1}} floor) ,最后我们即可得到相应边 ((u,v)) 容量为 (c(u,v)) 对应的最小费用流。

    复杂度:如果采用 SPFA ,那么最坏复杂度是 (O(NM^2logU)) 。((U) 为边的最大容量)

    使用 Dijkstra 的复杂度可能会优秀一点,但是编写难度较大,可以看看下面引用资料 1,介绍较为详细。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    inline int read()
    {
    	int x=0,y=1;char c=getchar();
    	while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
    	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    	return x*y;
    }
    
    const int N=205, M=10005<<1, INF=0x3f3f3f3f;
    
    int n, m, S, T;
    struct node{
    	int to, c, rc, w, next; // c 表示容量(进行 capacity scaling ),rc 表示原图容量,w 表示费用。
    }e[M];
    
    int h[N], tot;
    
    void add(int u, int v, int rc, int w){
    	e[tot].to=v, e[tot].rc=rc, e[tot].w=w, e[tot].next=h[u], h[u]=tot++;
    	e[tot].to=u, e[tot].rc=0, e[tot].w=-w, e[tot].next=h[v], h[v]=tot++;
    }
    
    int d[N], pre[N];
    bool vis[N];
    int q[N];
    
    void spfa(int s){
    	memset(vis, false, sizeof vis);
    	memset(d, 0x3f, sizeof d);
    	memset(pre, -1, sizeof pre);
    	int tt=0, hh=0;
    	d[s]=0, vis[s]=true, q[tt++]=s;
    	
    	while(tt!=hh){
    		int hd=q[hh++]; if(hh==N) hh=0; 
    		vis[hd]=false;
    		
    		for(int i=h[hd]; ~i; i=e[i].next){
    			int go=e[i].to;
    			if(e[i].c && d[go]>d[hd]+e[i].w){
    				d[go]=d[hd]+e[i].w;
    				pre[go]=i;
    				if(!vis[go]){
    					vis[go]=true;
    					q[tt++]=go; if(tt==N) tt=0;
    				}
    			}
    		}
    	}
    }
    
    void add_one_cap(int id){
    	// 优化,有流量的话,不可能关于这条边还存在负圈,直接更新后 return。
    	if(e[id].c){
    		e[id].c++;
    		return;
    	}
    	int u=e[id^1].to, v=e[id].to; // from and to
    	spfa(v);
    	if(d[u]<INF && d[u]+e[id].w<0){
    		e[id^1].c++;
    		int x=u;
    		while(x!=v){
    			int t=pre[x];
    			e[t].c--, e[t^1].c++;
    			x=e[t^1].to;
    		}
    	}else e[id].c++;
    }
    
    int main(){
    	memset(h, -1, sizeof h);
    	n=read(), m=read(), S=read(), T=read();
    	
    	while(m--){
    		int u, v, rc, w; u=read(), v=read(), rc=read(), w=read();
    		add(u, v, rc, w);
    	}
    	
    	add(T, S, INF, -INF);
    	
    	for(int i=10; i>=0; i--){
    		for(int j=0; j<tot; j++) e[j].c<<=1;
    		for(int j=0; j<tot; j+=2) if(e[j].rc>>i&1) add_one_cap(j); // 传入边的编号 id,进行 +1 容量操作。
    	}
    	
    	int cost=0;
    	for(int i=0; i<tot-2; i+=2) cost+=e[i].w*e[i^1].c;
    	cout<<e[tot-1].c<<' '<<cost<<endl;
    	
    	return 0;
    }
    

    当然,如果题目中不存在负圈,那么直接使用 SPFA 即可。

    ```cpp
    #include<bits/stdc++.h>
    using namespace std;
    
    inline int read()
    {
    	int x=0,y=1;char c=getchar();
    	while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
    	while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    	return x*y;
    }
    
    const int N=205, M=10005<<1, INF=0x3f3f3f3f;
    
    int n, m, S, T;
    struct node{
    	int to, c, rc, w, next; // c 表示容量(进行 capacity scaling ),rc 表示原图容量,w 表示费用。
    }e[M];
    
    int h[N], tot;
    
    void add(int u, int v, int rc, int w){
    	e[tot].to=v, e[tot].rc=rc, e[tot].w=w, e[tot].next=h[u], h[u]=tot++;
    	e[tot].to=u, e[tot].rc=0, e[tot].w=-w, e[tot].next=h[v], h[v]=tot++;
    }
    
    int d[N], pre[N];
    bool vis[N];
    int q[N];
    
    void spfa(int s){
    	memset(vis, false, sizeof vis);
    	memset(d, 0x3f, sizeof d);
    	memset(pre, -1, sizeof pre);
    	int tt=0, hh=0;
    	d[s]=0, vis[s]=true, q[tt++]=s;
    	
    	while(tt!=hh){
    		int hd=q[hh++]; if(hh==N) hh=0; 
    		vis[hd]=false;
    		
    		for(int i=h[hd]; ~i; i=e[i].next){
    			int go=e[i].to;
    			if(e[i].c && d[go]>d[hd]+e[i].w){
    				d[go]=d[hd]+e[i].w;
    				pre[go]=i;
    				if(!vis[go]){
    					vis[go]=true;
    					q[tt++]=go; if(tt==N) tt=0;
    				}
    			}
    		}
    	}
    }
    
    void add_one_cap(int id){
    	// 优化,有流量的话,不可能关于这条边还存在负圈,直接更新后 return。
    	if(e[id].c){
    		e[id].c++;
    		return;
    	}
    	int u=e[id^1].to, v=e[id].to; // from and to
    	spfa(v);
    	if(d[u]<INF && d[u]+e[id].w<0){
    		e[id^1].c++;
    		int x=u;
    		while(x!=v){
    			int t=pre[x];
    			e[t].c--, e[t^1].c++;
    			x=e[t^1].to;
    		}
    	}else e[id].c++;
    }
    
    int main(){
    	memset(h, -1, sizeof h);
    	n=read(), m=read(), S=read(), T=read();
    	
    	while(m--){
    		int u, v, rc, w; u=read(), v=read(), rc=read(), w=read();
    		add(u, v, rc, w);
    	}
    	
    	add(T, S, INF, -INF);
    	
    	for(int i=32; i>=0; i--){ // 取决于 logU 的大小
    		for(int j=0; j<tot; j++) e[j].c<<=1;
    		for(int j=0; j<tot; j+=2) if(e[j].rc>>i&1) add_one_cap(j); // 传入边的编号 id,进行 +1 容量操作。
    	}
    	
    	int cost=0;
    	for(int i=0; i<tot-2; i+=2) cost+=e[i].w*e[i^1].c;
    	cout<<e[tot-1].c<<' '<<cost<<endl;
    	
    	return 0;
    }
    

    引用资料

    1. https://ouuan.github.io/post/%E5%9F%BA%E4%BA%8E-capacity-scaling-%E7%9A%84%E5%BC%B1%E5%A4%9A%E9%A1%B9%E5%BC%8F%E5%A4%8D%E6%9D%82%E5%BA%A6%E6%9C%80%E5%B0%8F%E8%B4%B9%E7%94%A8%E6%B5%81%E7%AE%97%E6%B3%95/

    2. 《挑战程序设计竞赛》 P222-225

  • 相关阅读:
    C++ 引用详解
    QT的UDP组播技术
    idea快捷键
    window10安装不同版本的mysql(5.7和8.0.25)
    第2篇scrum
    结对项目:四则运算(C语言)
    个人项目wc(C语言)
    修改博客园背景,css
    第一次作业
    第4篇 Scrum 冲刺博客(专✌️团队)
  • 原文地址:https://www.cnblogs.com/Tenshi/p/14766579.html
Copyright © 2011-2022 走看看