• 初等网络流初步


    标题名字乱取的,吐槽一下《初等数论初步》的命名。

    1 网络流的概念

    1.1 引入

    “分配”是生产生活中一类常见的问题。如何分配才最好,是一个有技术含量的问题。
    对于“分配最优”的问题,动态规划是一种有效的解决方法。但是,有些分配无法描述出一个准确的“决策”“状态”“阶段”轮廓。我们来看下面这样一个简单的问题:

    一个研究性学习小组收到了一个含有25个任务的课题,组内一共有甲,乙,丙,丁,戊五个同学。他们工作的效率和工作的态度(最多愿意做多少任务)各不相同,具体如下:

    姓名 任务数上限 单个任务耗时
    4 42min
    5 56min
    6 71min
    7 108min
    8 121min

    假设任务不能同时做,求完成所有任务所需的最小时间。

    这个问题似乎不太好用动态规划。以前(i)个人的顺序划分阶段?似乎不太合适。这里人与人之间的关联似乎不大。

    这个问题可以用多维的线性规划来做,也可以考虑用网络流来解决。这是图论中一个重要的算法。

    为方便描述,现选定一个组长。我们来看这个简单的流程图:
    流程图
    这个流程图就像一个一个生产线,25个任务从老师那里出来,最后又回到老师那里去了。当然,回去的是完成的任务。
    可以对任务作出如下分配:
    分配
    如图,每一个箭头上都标上了每个人分配的任务,总共耗时33小时13分钟。

    可以看到,如果把每个人看成一个节点,那么人只负责“加工”任务,不会“储存”任务。这是一个非常重要的特征。更进一步的,我们可以提炼出其中的数学模型。

    1.2 定义

    一个网络(G)是一个有向图,其每一条边((x,y))可以用一个非负数(c(x,y))表示这条边的“容量”(capacity)。规定如果((x,y))不是这个图中的边,则(c(x,y)=0)。有些时候,网络中的边又叫做

    每一个这样的网络中都有两个特殊的节点:源点(S)汇点(T)

    设函数(f(u,v))是作用在边((u,v))上的函数,满足:
    1.(f(u,v) leq c(u,v))
    2.(f(u,v) = -f(v,u))
    3.(forall x eq S, x eq T, sum f(u,x) = sum f(x,v))
    那么称(f)是网络(G)的一个流函数。对于一个边((u,v))(f(u,v))就是它的流量

    以上这三个性质分别称为容量限制斜对称流量守恒。请结合“水流”和“河道”的关系加强理解:水流不会超过河道,且不可压缩。
    容量限制还有非常棒的一点:它省去了我写(sum)下标的麻烦,因为不在边上的流量一定是(0)。(当然,这样做未必标准规范。)

    在上面的任务分配中,从“组长”到“甲”的流量为(4),从“组长”到“乙”的流量为(5),以此类推。

    源点(S)和汇点(T)比较特殊,它们可以“发送”和“储存”流量。源点向整个网络运输的流量就是整个网络的流量,大小为(sum f(S,v))

    网络流还有一个有趣的性质:选取一个不含源点、汇点的点集,这个点集也满足流量平衡!这个性质可以看做是高斯定理的“离散版本”,在之后的网络流建模中会有所体现。

    网络流的思想非常巧妙;正因如此,用网络流解题不像其他数据结构,是需要花费一点心思的。如何构建网络?如何转换问题?这些都是需要考虑的。

    2 有关算法

    2.1 最大流算法

    网络中的最大流量就叫最大流。一个网络的流函数有很多;但幸运的是,我们可以避免直接对流函数操作,采用一种巧妙的方法。

    2.1.1 Edmonds-Karp增广路算法

    我们关注源点发送的其中一股流量。这股流量通过一条路径,最终流向了汇点。这样的路径叫做增广路。不难想到,路上的流量(f)等于路上容量的最小值(min c)

    仔细一想,是不是源点发送的流量都会经过若干条增广路,然后全部流向汇点?我们只要尽可能地找出增广路,就可以求出最大流了!

    这就是Edmonds-Karp算法的雏形了。通过BFS,我们可以找到一条增广路,并求出这条路上的流量。如何再找出一条增广路呢?再做一次BFS会返回同样的路径,而直接维护边上的信息又不太方便。

    我们考虑直接修改增广路上的容量:求出流量(f)后,对路上的每一条边((x,y)),都执行(c(x,y)=c(x,y)-f),防止新的增广路超过容量。请注意,在原来流量(f)的基础上,我们还可能构造一个“逆流”(f'),使得某个(f(x,y))直接与(f'(x,y))抵消了。这样做是可行的,需要考虑。

    通过执行(c(x,y)-=f)(c(y,x)+=f)后,得到的新网络叫做残量网络。在这个网络上添加的流量,一定不会超过原来的容量。

    综上,通过BFS探测+构造残量网络的形式,我们可以分别求得若干个可行的流量(f,f',f'',cdots);相加就得到了整个网络的最大流。

    首先是搜索增广路的部分:

    inline bool find_argumenting_path()
    {
    	memset(visit, 0, sizeof(visit));//标记
    	queue<int> q; q.push(S); inq[S] = Inf; visit[S] = true;
    	
    	while(!q.empty())
    	{
    		int cur = q.front(); q.pop();
    		for(rg int e = head[cur]; e; e = edge[e].next)
    		{
    			if(edge[e].capacity <= 0)
    				continue;
    			
    			int to = edge[e].to;
    			if(visit[to] == true)
    				continue;
    			
    			inq[to] = min(inq[cur], edge[e].capacity);
    			pre_edge[to] = e;//标记边,方便记录路径
    			q.push(to); visit[to] = true;
    			if(to == T)
    				return true;
    		}
    	}
    	return false;
    }
    

    注意到这里我们用到了一个inq数组,它表示“某个节点的剩余流量”。可以这样理解:我们在源点处注入足够多的水,当水沿着边流动时,其一定要收到边容量的限制。最终到达汇点的水量就是我们本次需要增加的流量。因此,对于边((x,y)),一定有inq[y]=min(inq[x], edge[e].capacity)

    一旦找到了增广路,我们就将本次流量加入到答案中,并更新残量网络:

    inline void update()
    {
    	int cur = T;
    	while(cur != S)
    	{
    		int e = pre_edge[cur];
    		edge[e].capacity -= inq[T];
    		edge[e^1].capacity += inq[T];
    		cur = edge[e].front;
    	}
    	max_flow += inq[T];
    }
    

    根据之前的pre标记,我们可以还原一条从T到S的路径,并调整路径上的边的容量。

    最后在主程序中代码如下:

    while(find_argumenting_path() == true)
    	update();
    

    2.2.2 Dinic算法

    2.2 最小割

    2.3 简单费用流

    3 一些练习

  • 相关阅读:
    BOJ 85 Three Points On A Line
    BOJ 84 Single Number
    BOJ 83 A + B Problem
    【转载】运算符优先级
    匹配体重和为特定值的人,两两成对
    The Brand New Beginning!
    【失败】制作CentOS镜像
    【制作镜像】安装VMwareTool
    部署巡检脚本
    windows server 2008镜像重启后密码变为默认密码的问题的解决方案
  • 原文地址:https://www.cnblogs.com/LinearODE/p/11228258.html
Copyright © 2020-2023  润新知