zoukankan      html  css  js  c++  java
  • 网络流

    网络流

    网络流概念

       在一个有向图上选择一个源点,一个汇点,每一条边上都有一个流量上限(以下称为容量),

       即经过这条边的流量不能超过这个上界,同时,除源点和汇点外,所有点的入流和出流都相等,

       而源点只有流出的流,汇点只有汇入的流。这样的图叫做网络流。

    相关定义

    1. 源点:有n个点,有m条有向边,有一个点很特殊,只出不进,叫做源点。

    1. 汇点:另一个点也很特殊,只进不出,叫做汇点。

    2. 容量和流量:每条有向边上有两个量,容量和流量,从i到j的容量通常用c[i,j]表示,流量则通常是f[i,j].

      通常可以把这些边想象成道路,流量就是这条道路的车流量,容量就是道路可承受的最大的车流量。

      很显然的,流量<=容量。而对于每个不是源点和汇点的点来说,可以类比的想象成没有存储功能的货物的中转站,

      所有“进入”他们的流量和等于所有从他本身“出去”的流量。

    4. 最大流:把源点比作工厂的话,问题就是求从工厂最大可以发出多少货物,是不至于超过道路的容量限制,也就是,最大流。

    增广路算法

      该方法通过寻找增广路来更新最大流,有 EK,dinic,SAP,ISAP 主流算法。

      求解最大流之前,我们先认识一些概念。

      最常用的就是dinic 但是费用流需要用到EK算法,so,要学会EK算法,dinic;

      增广路:在图中若一条从源点到汇点的路径上所有边的 剩余容量都大于 0 (注意是大于不是大于等于),这条路被称为增广路。

    求解思路

      从图中找一条增广路,然后增广,怎么找?

      1.从源点开始bfs,找到到汇点的一条路径,并记录这条路径上所有边剩余流量最小值,因为要找增广

      路,所以我们在bfs时要判断一下边的剩余容量是否为0,记得用一个pre数组记录下路径。

      2.找到路径后,对其进行增广(代码里的up函数),增广就是把这条路径的每条边都减去这

      些边中剩余流量的最小值(bfs时记录),反向边加上这个最小值(关于方向边下面再解释)。

      3.一直找增广路增广,直到不能增广为止(找不到增广路)。

      可以看下面这张图。

    上面我们提到了反向边,下面我们解释下为什么要建反向边。(放图,简单图我还是可以的现学的)

     像我们上面这张图,因为我们bfs时不能确定第一次走哪条边,要是你像bmf一样运气不好,

     如果bfs第一次找到的增广路是1→3→2→1的话,我们最后求得的最大流就是1.

     但是很明显这张图最大流是2,所以我们发现第一次找的增广路不是最优的,这时候你就凉了。

     那我们怎么解决呢反向边,反向边其实就是一个反悔的机会,也就是让流过的流量流回去。

     如果还不明白的话还学什么网络流,下面模拟一下这个过程。

     先说一下反向边的一些东西,反向边初始化为0,当正向边剩余流量减少的是时候,

     反向边流量增加同样的值,因为我们可以反悔的流量等于已经从这条边正向流过的流量。

     下面看一下我们是如何通过反向边反悔的又要做图qaq。

     因为我不会画反向边,所以我们假设‘1/0’左边那个数字表示正向变边权,右边是反向边。

     下面这张图就是建完边后的图。

     如果我们第一次找到的增广路是1→3→2→1的话,总流量+1=1,图就变成了

     我们发现我们还可以找到增广路1→2→3→4,总流量+1=2,图变成

     然后发现,找不到增广路了,程序结束不用作图了,我们发现

    再找增广路的过程中3→2这条边正向一次,反向一次,相当于流量为0.

    这就是反向边的作用。

    另外提供一种小技巧,使用邻接表建图的话,可以边的编号从2开始建,我们知道

    21=3,31=2……

    这样的话我们可以通过异或得到反向边的编号(记得建完正向边,紧接着就建反向边),具体看代码

    时间复杂度为O(nm2)至于为什么,本人很菜不会,另外,一般时间复杂度是远远达不到这个值的。

    代码(由于本人没写过EK,所以从同学那扒了一份)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define LL long long
    using namespace std;
    const int inf=1<<29;
    const int N=207;
    const int M=5e3+7;
    int n,m,s,t,cnt=1;//从编号2开始建边 
    LL maxf;//最大流 
    int head[N],vis[N],pre[N];
    LL incf[N];
    LL v[N][N]; 
    struct edge{
        int v,nxt;
        LL w;
    }e[M<<1];//因为要建反向边,所以开二倍空间 
    void add_edge(int u,int v,LL w){//存边 
        cnt++;
        e[cnt].v=v;
        e[cnt].w=w;
        e[cnt].nxt=head[u];
        head[u]=cnt;
    }
    bool bfs(){
        memset(vis,0,sizeof(vis));
        queue<int>q;
        q.push(s);
        vis[s]=1;
        incf[s]=inf;
        while(q.size()){
            int now=q.front();q.pop();
            for(int i=head[now];i;i=e[i].nxt){
                if(e[i].w==0||vis[e[i].v])continue;
                int to=e[i].v;
                incf[to]=min(incf[now],e[i].w);//记录路径最小边的流量 
                pre[to]=i;//记录路径边的编号 
                q.push(to);
                vis[to]=1;
                if(to==t)return 1;
            }
        }
        return 0;
    }
    void up(){
        int x=t;
        while(x!=s){
            int i=pre[x];
             e[i].w-=incf[t];
             e[i^1].w+=incf[t];//反向边加上正向边减少的流量 
             x=e[i^1].v;
        }
        maxf+=incf[t];
    } 
    inline int read()
    {
        int x = 0 , f = 1;  char ch = getchar();
        while(ch < '0' || ch > '9') {if(ch == '-')  f = -1; ch = getchar();}
        while(ch >= '0' && ch <= '9') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
        return x * f;
    }
    int main(){
        n = read(); m = read(); s = read(); t = read();
        for(int i=1;i<=m;i++){
            int x,y,z;
            x = read(); y = read(); z = read();
            v[x][y] += z;
        }
        for(int i = 1;i <= n;i ++) for(int j = 1;j <= n;j ++) if(v[i][j]) add_edge(i,j,v[i][j]),add_edge(j,i,0);
        while(bfs())up();
        cout<<maxf<<endl;
    }
    

    //码风过丑,请联系Aswert 这锅我不背

    Dinic 算法

    学了EK还学dinic有什么用呢,有用,我们来分析下下面这张图不小心把颜色改了下,懒得再做一张。

    如果你运气不好的话像bmf一样,若你每次找到的增广路都经过了2→3或3→2这条边的话你又凉了

    所以这时候就用到了我们的Dinic算法。

    Dinic 算法 的过程是这样的:每次增广前,我们先用 BFS 来将图分层。设源点的层数为1 ,

    那么一个点的层数便是它离源点的最近距离。

    层次用数组dep表示。分层图对于每条边满足dep[v]=dep[u]+1。

    我们思考,EK算法每轮可能会遍历整个图,但只找出一条增广路,属于单路增广。

    而Dinic属于多路增广,时间复杂度O(n2m)

    求解思路

    1.bfs求出节点的层次,构建分层图

    2.对于求出的分层图,用dfs进行多路增广,由于本人菜,讲的不是很明白,我们可以看代码

    3.当前弧优化 :cur[]数组的应用,如果一条边已经被增广过,那么它就没有可能被增广第二次。那么,我们下一次进行增广的时候,

    就可以不必再走那些已经被增广过的边。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    #define LL long long
    const int INF = 2333333;
    const int N = 210;
    int n,m,x,y,w,s,t,tot = 1;
    LL maxflow;
    int dep[N],head[N];
    struct node{
    	int to , net , w;
    }e[1000010];
    inline int read()
    {
    	int s = 0 , w = 1; char ch = getchar();
    	while(ch < '0' || ch > '9'){if(ch == '-')w = -1; ch = getchar();}
    	while(ch >= '0' && ch <= '9'){s = s * 10+ch-'0'; ch = getchar();}
    	return s * w;
    }
    void add(int x, int y , int w)
    {
    	e[++tot].w = w;
    	e[tot].to = y;
    	e[tot].net = head[x];
    	head[x] = tot;
    }
    bool bfs()//构建分层图 
    {
    	memset(dep , 0 , sizeof(dep));//每个节点层次初始化 
    	queue<int> q;
    	q.push(s); dep[s] = 1;//源点初始化为1; 
    	while(!q.empty())
    	{
    		int x = q.front(); q.pop();
    		for(int i = head[x]; i; i = e[i].net)
    		{
    			int to = e[i].to;
    			if(e[i].w && !dep[to])//构建分层图的时候要保证边不为0,如果dep[]已经被更新就不用更新了 
    			{
    				q.push(to);
    				dep[to] = dep[x] + 1;
    				if(to == t) return 1;//如果到达汇点,进行dfs 
    			}
    		}
    	}
    	return 0;
    }
    int dinic(int x , int flow)
    {
    	if(x == t) return flow;
    	int rest = flow , k;//rest表示当前这个节点最大允许通过流量 
    	for(int i = head[x]; i && rest; i = e[i].net)
    	{
    		int to = e[i].to; int val = e[i].w;
    		if(val && (dep[to] == dep[x] + 1))//寻找增广路 
    		{
    			k = dinic(to , min(rest , val));
    			if(!k) dep[to] = 0;//如果在to之后的路径找不到增广路,踢出分层图 
    			e[i].w -= k; e[i^1].w += k;
    			rest -= k;//当前节点最大允许通过流量减去这次通过的流量 
    		}
    	}
    	return flow - rest;
    }
    int main()
    {
    	n = read(); m = read(); s = read(); t = read();
    	for(int i = 1; i <= m; i++)
    	{
    		x = read(); y = read(); w = read();
    		add(x,y,w); add(y,x,0);
    	}
    	int flow = 0;
    	while(bfs())
    	{
    		while(flow = dinic(s,INF)) maxflow += 1LL*flow;
    	}
    	printf("%lld\n",maxflow);
    	return 0;
    }
    

    最小割

    最小割问题是指:给出一种有向图(无向图)和两个点s,t以及图中的边的边权,

    求一个权值和最小的边集,使得删除这些边之后是s,t不连通。这类问题,一般运用最

    大流等于最小流定理,求出最大流来解决。证明自行百度百科

    代码同上。

    转载于jbh大佬的博客
    ENDing!

  • 相关阅读:
    使用简单的反射技术重构组合查询串功能
    沤血分享之:使用Opera浏览器技巧全集
    项目中用到的RE分析
    关于调用新浪微博与腾讯微博
    正则 (?i,m,s,x,g)
    求职路 第二章 深圳篇
    12320平台架构及部署
    网站会员密码
    求职路 第二章 技术篇
    TFS故障一二
  • 原文地址:https://www.cnblogs.com/genshy/p/13369027.html
Copyright © 2011-2022 走看看