最大流(网络流)基础篇(剪辑)

网络流初步总结

查看资料:lrj 《算法竞赛入门经典》

相关概念:

最大流:(Maximum-Flow Problem)

从源点 S  中间经过一些点,一些的物品运送到汇点 t 。

中途每两点间都有个最大运送物品数。

求从 s 到 t 最多能运送多少物品。

容量: 对于一条边 (u,v),它的物品上限(能够运送的物品最大数量)称为容量 (capacity),

记为 c(u,v) (对于不存在的边 (u,v) , c(u,v) = 0)

流量: 实际运送物品数称为流量 (flow)

规定:f(u,v) 和 f(v,u) 最多只有一个正数(可以均为 0),且 f(u,v) = - f(v,u)

PS:此图左边表示实际运送物品,右边表示最大容量。

结论:对于除了 s 和 t 的任意节点 u,  ∑ f(u,v)  = 0 (有些 f 为负数) 。

(u,v)∈E

最大流问题中: 容量 c 和 流量 f 满足三个性质

容量限制 f(u,v) <= c(u,v)

斜对称:f(u, v) = -f(u,v)

流量平衡:对于除了 s 和 t 的任意节点 u,  ∑ f(u,v)  = 0 (有些 f 为负数) 。

(u,v)∈E

目标:最大化 | f |  = ∑ f(s,v)       =    ∑ f(u,t)             即从 S 点流出的净流量(=流入 t 点的净流量)

(s,v)∈E ,        
(u,t)∈E

增广路算法:

残量:上图中每条边上的容量差 (称为残余流量,简称残量),

比如说上面第二个图中 V2 到 V4 残量为 14-11 = 3; V4 到 V2 残量为 0-(-11)= 11

算法基于事实:

残量网络中任何一个从 s 到 t 的有向道路都对应一条原图中的增广路【PS:不理解这个名词也没事继续看】。

只要求出该道路中所有残量的最小值 d,把对应的所有边上的流量增加 d 即可,这个过程称为增广。

也就是说只要有从起点 s 到终点 t 的路上存在流量,那么找出最小的残余流量 d

那么这个 d 肯定是满足这条路径的每一条边的,否则找不出这样的 d

那么这条路径上的每一条边的流量增加 d ,总流量增加 d 就好了。

然后继续找,直到找不到为止。

不难证明如果增广前的流量满足 3 个条件,那么增广之后任然满足。

显然只要残量网中存在增广路,流量就可以增大。

逆命题:如果残量网中不存在增广路,则当前流就是最大流,这就是著名的增广路定理。

问题:如何找路径? DFS ms 很慢,用 BFS

    queue<int> q;
    memset(flow,0,sizeof(flow)); //初始化流量为 0
    f = 0; // 初始化总流量为 0
    for(;;) //BFS 找增广路
    {
        memset(a,0,sizeof(a)); // a[i]:从起点 s 到 i 的最小残量【每次for()时 a[] 重新清 0 因此同时可做标记数组 vis】
        a[s] = INF; //起点残量无线大
        q.push(s);  //起点入队
        while(!q.empty()) // BFS 找增广路
        {
            int u = q.front(); //取队首
            q.pop(); // 出队
            for(int v = 1; v <= n; v++) if(!a[v] && cap[u][v] > flow[u][v]) //找新节点 v
            {
                p[v] = u; q.push(v); //记录 v 的父亲节点,并加入 FIFO 队列
                a[v] = min(a[u], cap[u][v]-flow[u][v]); // s-v 路径上的最小残量【从而保证了最后,每条路都满足a[t]】
            }
        }  

        if(a[t] == 0) break; // 找不到,则当前流已经是最大流 【t为终点】  

        for(int u = t; u != s; u = p[u]) // 从汇点往回走
        {
            flow[p[u]][u] += a[t]; // 更新正向流
            flow[u][p[u]] -= a[t]; // 更新反向流
        }
        f += a[t]; // 更新从 S 流出的总流量
    }

推荐入门题目:

hdu 3549 Flow Problem【最大流增广路入门模板题】


最大流模板

    const int MAXN=20010;//点数的最大值
    const int MAXM=880010;//边数的最大值
    const int INF=0x3f3f3f3f;  

    struct Node
    {
        int from,to,next;
        int cap;
    }edge[MAXM];
    int tol;
    int head[MAXN];
    int dep[MAXN];
    int gap[MAXN];//gap[x]=y :说明残留网络中dep[i]==x的个数为y  

    int n;//n是总的点的个数,包括源点和汇点  

    void init()
    {
        tol=0;
        memset(head,-1,sizeof(head));
    }  

    void addedge(int u,int v,int w)
    {
        edge[tol].from=u;
        edge[tol].to=v;
        edge[tol].cap=w;
        edge[tol].next=head[u];
        head[u]=tol++;
        edge[tol].from=v;
        edge[tol].to=u;
        edge[tol].cap=0;
        edge[tol].next=head[v];
        head[v]=tol++;
    }
    void BFS(int start,int end)
    {
        memset(dep,-1,sizeof(dep));
        memset(gap,0,sizeof(gap));
        gap[0]=1;
        int que[MAXN];
        int front,rear;
        front=rear=0;
        dep[end]=0;
        que[rear++]=end;
        while(front!=rear)
        {
            int u=que[front++];
            if(front==MAXN)front=0;
            for(int i=head[u];i!=-1;i=edge[i].next)
            {
                int v=edge[i].to;
                if(edge[i].cap!=0||dep[v]!=-1)continue;
                que[rear++]=v;
                if(rear==MAXN)rear=0;
                dep[v]=dep[u]+1;
                ++gap[dep[v]];
            }
        }
    }
    int SAP(int start,int end)
    {
        int res=0;
        BFS(start,end);
        int cur[MAXN];
        int S[MAXN];
        int top=0;
        memcpy(cur,head,sizeof(head));
        int u=start;
        int i;
        while(dep[start]<n)
        {
            if(u==end)
            {
                int temp=INF;
                int inser;
                for(i=0;i<top;i++)
                   if(temp>edge[S[i]].cap)
                   {
                       temp=edge[S[i]].cap;
                       inser=i;
                   }
                for(i=0;i<top;i++)
                {
                    edge[S[i]].cap-=temp;
                    edge[S[i]^1].cap+=temp;
                }
                res+=temp;
                top=inser;
                u=edge[S[top]].from;
            }
            if(u!=end&&gap[dep[u]-1]==0)//出现断层,无增广路
              break;
            for(i=cur[u];i!=-1;i=edge[i].next)
               if(edge[i].cap!=0&&dep[u]==dep[edge[i].to]+1)
                 break;
            if(i!=-1)
            {
                cur[u]=i;
                S[top++]=i;
                u=edge[i].to;
            }
            else
            {
                int min=n;
                for(i=head[u];i!=-1;i=edge[i].next)
                {
                    if(edge[i].cap==0)continue;
                    if(min>dep[edge[i].to])
                    {
                        min=dep[edge[i].to];
                        cur[u]=i;
                    }
                }
                --gap[dep[u]];
                dep[u]=min+1;
                ++gap[dep[u]];
                if(u!=start)u=edge[S[--top]].from;
            }
        }
        return res;
    }

给边赋值时,养成习惯用加法,防止有重边!

    //****************************************************
    //最大流模板
    //初始化:g[][],start,end
    //******************************************************
    const int MAXN=110;
    const int INF=0x3fffffff;
    int g[MAXN][MAXN];//存边的容量,没有边的初始化为0
    int path[MAXN],flow[MAXN],start,end;
    int n;//点的个数,编号0-n.n包括了源点和汇点。  

    queue<int>q;
    int bfs()
    {
        int i,t;
        while(!q.empty())q.pop();//把清空队列
        memset(path,-1,sizeof(path));//每次搜索前都把路径初始化成-1
        path[start]=0;
        flow[start]=INF;//源点可以有无穷的流流进
        q.push(start);
        while(!q.empty())
        {
            t=q.front();
            q.pop();
            if(t==end)break;
            //枚举所有的点,如果点的编号起始点有变化可以改这里
            for(i=0;i<=n;i++)
            {
                if(i!=start&&path[i]==-1&&g[t][i])
                {
                    flow[i]=flow[t]<g[t][i]?flow[t]:g[t][i];
                    q.push(i);
                    path[i]=t;
                }
            }
        }
        if(path[end]==-1)return -1;//即找不到汇点上去了。找不到增广路径了
        return flow[end];
    }
    int Edmonds_Karp()
    {
        int max_flow=0;
        int step,now,pre;
        while((step=bfs())!=-1)
        {
            max_flow+=step;
            now=end;
            while(now!=start)
            {
                pre=path[now];
                g[pre][now]-=step;
                g[now][pre]+=step;
                now=pre;
            }
        }
        return max_flow;
    }

最大流(网络流)基础篇(剪辑)

时间: 2024-10-08 19:34:55

最大流(网络流)基础篇(剪辑)的相关文章

java基础篇IO流的规律

前两篇降了IO流中的字节流和字符流复制的例子,今天来总结一下IO流的规律 掌握好IO流的规律,再开发中会很好用 下面来总结一下: 1,明确源和目的 源:输入流 InputStream 和Reader 目的:输出流 OutputStream 和Writer 2,操作的数据是否是纯文本. 是:使用字符流 不是:使用字节流 3,当体系明确后,在明确要使用哪个具体的对象,通过设备来进行区分 源设备: 内存,硬盘,键盘 目的设备: 内存,硬盘,控制台 这里的源就是你想进行的操作,比如说你想从c盘复制一个文

Spring Cloud Alibaba | Sentinel: 服务限流基础篇

目录 Spring Cloud Alibaba | Sentinel: 服务限流基础篇 1. 简介 2. 定义资源 2.1 主流框架的默认适配 2.2 抛出异常的方式定义资源 2.3 返回布尔值方式定义资源 2.4 注解方式定义资源 2.5 异步调用支持 3. 规则的种类 3.1 流量控制规则 (FlowRule) 3.2 熔断降级规则 (DegradeRule) 3.3 系统保护规则 (SystemRule) 3.4 访问控制规则 (AuthorityRule) Spring Cloud Al

网络流基础

1.流网络G=(V,E)是一个有向图,其中每条边(u,v)∈E均有一个非负容量 c(u,v)>=0.如果(u,v)不属于E,则假定c(u,v)=0.流网络中有两个特别的顶点:源点s和汇点t.下图展示了一个流网络的实例 (其中斜线左边的数字表示实际边上的流,右边的数字表示边的最大容量): 对一个流网络G=(V,E),其容量函数为c,源点和汇点分别为s和t.G的流f满足下列三个性质:      容量限制:对所有的u,v∈V,要求f(u,v)<=c(u,v).      反对称性:对所有的u,v∈V

Spark性能优化指南——基础篇

前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作,应用范围与前景非常广泛.在美团•大众点评,已经有很多同学在各种项目中尝试使用Spark.大多数同学(包括笔者在内),最初开始尝试使用Spark的原因很简单,主要就是为了让大数据计算作业的执行速度更快.性能更高. 然而,通过Spark开发出高性能的大数据计算作业,并不是那么简单的.如果没有对Spar

java基础篇---I/O技术(三)

接上一篇java基础篇---I/O技术(二) Java对象的序列化和反序列化 什么叫对象的序列化和反序列化 要想完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream).使用对象输出流输出序列化对象的步骤,有时也成序列化,而使用对象输入流读入对象的过程,有时也称为反序列化 一个对象产生之后实际上是在内存中为其开辟了一个存储空间,方便存储信息. 对象序列化就是把一个对象变成二进制的数据流的一个方法,通过对象序列化可以反驳的

SQL Server调优系列基础篇(联合运算符总结)

前言 上两篇文章我们介绍了查看查询计划的方式,以及一些常用的连接运算符的优化技巧,本篇我们总结联合运算符的使用方式和优化技巧. 废话少说,直接进入本篇的主题. 技术准备 基于SQL Server2008R2版本,利用微软的一个更简洁的案例库(Northwind)进行解析. 一.联合运算符 所谓的联合运算符,其实应用最多的就两种:UNION ALL和UNION. 这两个运算符用法很简单,前者是将两个数据集结果合并,后者则是合并后进行去重操作,如果有过写T-SQL语句的码农都不会陌生. 我们来分析下

Python基础篇(八)

key words:私有变量,类静态变量,生成器,导入Python模块,r查看模块可以使用的函数,查看帮助信息,启动外部程序,集合,堆,时间模块,random模块,shelve模块,文件读取等 >>> class Rectangle: ...     def __init__(self): ...         self.__width = 0 ...         self.__height = 0 ...     def setSize(self,width,height): .

awk(一)基础篇

上一篇总结了下sed的用法,这一篇玩玩sed的好基友awk,学完它俩,以后就再也不用怕处理文本文件了. awk其实和sed处理过程差不多,都是面向字符流的.sed和awk都相当于是一个加工厂,输入的文本行,相当于原材料,原材料在工厂中经过一系列处理.然后输出成品. awk和sed不同的是,awk更加的强大了,基本上可以说大多数sed能够实现的功能,awk都可以实现.... 但是实现相同的功能,大多情况下sed的过程更加的简洁吧. 当然awk还有着更多的功能,awk本身就是一门类C的语言了,有变量

美团Spark性能优化指南——基础篇

http://tech.meituan.com/spark-tuning-basic.html 前言 在大数据计算领域,Spark已经成为了越来越流行.越来越受欢迎的计算平台之一.Spark的功能涵盖了大数据领域的离线批处理.SQL类处理.流式/实时计算.机器学习.图计算等各种不同类型的计算操作,应用范围与前景非常广泛.在美团?大众点评,已经有很多同学在各种项目中尝试使用Spark.大多数同学(包括笔者在内),最初开始尝试使用Spark的原因很简单,主要就是为了让大数据计算作业的执行速度更快.性