最详细(也可能如今不是了)网络流建模基础

网络流建模方法一览

大锅博主终于找回来了原稿。。。。ios

哈,听说加上最详细这几个字会比较容易吸引人阅读。算法

资料来源:百度百科(笑~数组

​ 还有一些dalao的博客,不过也都只是由于不想打字了就copy一些很通俗的结论过来,全文几乎都是笔者的原创。网络

注:此文随着笔者的不断作题持续更新。数据结构

已经很熟悉网络流的dalao能够从七开始看,或许能有些收获。闭包

网络流的题目,大部分都是在考建模,不多有考你板子的。。。除非你非得用EK结果被卡上天。笔者从作过的全部网络流的题目里寻找了一些比较经常使用和经典的建模方式,一共你们学习。函数

那么就从简单到难一步步看吧。工具

1、傻逼建图法

笔者打上这两个字是绝对没有任何问题的,让咱们来想一想网络流的模板题的要求是什么。oop

(注:笔者选取了洛谷为题目来源,缘由竟是视觉效果友好)

很明显, 没有比这种题目更简单的网络流了,题目给你怎么连的边,你就照着题目连上就能够了,几乎是把你当作一个啥都不会的人手把手教你建图。。可是这种题目的数据范围通常较大,建议使用较高效率的网络流算法。学习

2、超级源点和汇点的设置

这应该是网络流题目中最最最最经常使用的一种方法了。常常有题目,咱们要求一些最小的利益什么的,可是明显没有可用的源点和汇点或者更多状况是题目中给出了不止一个源点和汇点,这时候就要创建超级源和超级汇,把全部的题目中的可行源点和汇点分别链接到超级源和超级汇上,而事实上,咱们拿到任何一道网络流题目,除非它的源点和汇点是严格给定的,咱们均可以尝试创建超级源汇从而达到减少建模难度的目的。

例题:飞行员匹配问题

题解:这个题咱们先无论输出方案,只看要输出的那个最大值,很明显,若是咱们把英国飞行员当成源点,外国飞行员当成汇点的话,会有不少源点和汇点,本着方便至上的原则,创建超级源汇,而后板子便可。
前面的题目这么简单笔者并不想放代码

3、最小割类问题

这实际上是一个很关键的定理。事实上真要细细的说这个总结里写的确定是不够用的。咱们在作题的时候,有时会遇到这样的一类问题,就是给你一个图,而后给你边权,问你如何在割掉的边权尽可能小的状况下把指定的两个点or两个集合分开。而后咱们就把你求得的这个最小的边权和叫作这个图的最小割。

那么补上以前不想证实的结论。

首先最大流==最小割

为何呢,想象一下,咱们如今有这么一个有向图,而后,最大流是小于等于割的,那么。。。在最大流=某个割的时候这个割就是最小的。。。感性理解一下的话,一个最大流跑到那个图上,这个图怎么看S也无法连到T去了。

而后最小割的惟一性?

咱们考虑一条边是否必定在最小割中。明显,咱们跑完一遍网络流以后残量网络里满流的边都是可能在最小割里的,而后又到了跑Tarjan的时候了,若是这条边在最小割里,那么跑完Tarjan以后它所链接的两个端点必定是在不一样的强连通份量里。若是是的话,用脑子想一想,割了这条边这个图仍是没分开啊。

那么咱们也能够由此得证,假设一条边所链接的点一个在S所在的强连通份量里,一个在T所在的强连通份量里的话,那么这条边必定在最小割里。

1.平面图最小割=最大流=对偶图最短路

而后就是转换成对偶图的最短路的问题。其实这已经不该该算在网络流的范畴里了(由于它变成了最短路)。那么就不在这里详述了,具体的能够去看看[BZOJ1001]狼抓兔子,虽然能够最大流全力优化蜜汁跑过。

咱们在这里仍是讨论最小割的问题

2.割边

这种就是咱们所说的最日常的最小割问题,咱们只须要直接求出图的最大流就能够了。

而后是一些高深一些的建模:有不少题目都会让你求这样一个东西,就是怎么安排这些balabala的工做能够得到balabala的利益,而后让你去求这个最大利益。不少人都会正常的去想最大流的作法可是因为有限制条件,因此不能像那么求。而后咱们不妨转换一下,若是咱们全部的均可以选,咱们是否是能够获得一个最大利益,而后咱们考虑的是放弃最小的利益和,这样咱们就把这个题转换成了一个最小割的问题,能够利用最大流求出。

而后随之而来的就是另外一个模型:最大权闭合图这类题目均可以转化为最小割来求解,具体证实仍是请百度吧,这篇文章并非介绍这类东西的,而且笔者对这些模型的证实也不如网上的神犇。如今笔者又回来证这个东西了。请向后翻阅,天然能找到。。

例题:Luogu P3410 拍照

题解:这题就是一个最大权闭合图的模型,求出总利益减去不可得的利益就能够了,连边的话比较容易就不详述了,不过再日后的题目应该就会有连边的思路了。毕竟前面这些都是些入门题目。

例题:方格取数问题

题解:咱们可能会有一个比较基础的贪心思想,没错,就是隔一个取一个,可是这么作并不可行,具体反例很容易找。而后咱们经过观察,发现这道题和某最大权闭合子图有些相似,若是咱们全取全部点,删去最小割说不许可行。 开始考虑建图,首先全部的奇数格子连源点,偶数格子连汇点,边权为点权。他们之间的边只连奇数到偶数的,边权为inf,这么连是为了不重复计算。而后直接DINIC最大流就能够了。

3.割点(?)

这也是最小割的一种经典问题,具体实现方式咱们等下放在拆点那个栏目里讲,这里就先不说了。

4、拆点

说实话,网络流最难思考的地方就在这里了,几乎全部拿得出来的经典的题目都是神奇的拆点而后求出最大流。

1.基础拆点——入点和出点

常常有这一类问题,一个图给出了点权而不是边权,咱们在链接边的时候就显得十分很差操做,这个时候咱们每每就会有这样一种操做,把每一个点拆成入点和出点,题目给出的连边均由每一个点的出点连向入点,而后每一个点的入点和出点之间连一条流量为点权的边,就能够知足点权的限制了。

而后让我把刚才的坑补一下:割点

咱们在不少时候都会发现,在求最小割的时候,咱们要割的不是边,而是割掉一个点。这时候咱们再用刚才的直接跑的方法很明显就不能够了。由于割掉一条边最多只影响两个点之间的连边,而割掉了一个点的话,与这个点相连的全部边就都失去了做用。这个时候咱们就能够先拆点,而后把这个点入点和出点之间的那条边割掉,就至关因而这个点在图中不存在了。

例题:教辅的组成

题解:本题的构图思路能够表示为:
源点->练习册->书(拆点)->答案->汇点
注意必定要拆点,由于一本书只能用一次。
因为题目难度不大,这个题就提点到这里了。

例题:奶牛的电信

为何找这道奶牛题。。觉得确实是割点的题目不是很好找,剩下的题目不大适合放在这个地方。若是你好好看了刚才的割点,这个题应该能够直接作出来了。不详述了。

2.按照时间拆点

有一部分题目是这样的,咱们给出的图的同时也给出了一个天数或者时间的限制,而后对每一天作出询问,最后求总和,很明显的一点是,要把每一天都连向汇点而后求出总和。这个时候咱们发现,若是其余的点仅仅只是一个点的话,没法知足求出每一天这个要求,由于会互相影响,这个时候咱们就相应的把这些点拆整天数这么多个点,而后分别向向对应的天数连边。例题的话一会会有一些笔者认为比较好的题集中放在一块儿,这里就先介绍建图方法了。

3.根据时间段拆点

这不是和上面那个同样么???实际上不是的,注意,刚才那个是时间,而这个是时间段。不过说实话这种拆点方式主要是在费用流里出现,由于有不少这种题目,就是一我的在不一样的流量是费用是不同的,因此为了知足这个要求,就把这我的拆成一共有多少不一样费用的点。举个例子:大家数学老师今天布置做业越多,你的不满值越大,若是是5张如下,一张卷子增长1点不满值,若是10张如下,一张卷子增长两点,若是10张以上,那么一张卷子增长inf点不满值。那么咱们就把你拆成三个点,分别对应三种不满值。

4.蛇皮拆点

有不少题目,拆点每每十分难想或者蛇皮至极有不少典型的例子就不一一详述了,好比什么一个点拆三个点四个点之类的。直接说一些例题貌似是比较好的。

[CQOI2012]交换棋子

这题不建议刚学费用流的人作,由于确实比较恶心,容易打击到本身。首先,咱们能够发现交换这个操做是很难去用流量描述和限制的。那咱们应该怎么办?若是咱们把格子上的全部黑棋子当棋子,而剩下的白子就当成空的格子,咱们就把一个交换的操做当成了一个移动的操做。那么如何限制流量呢?咱们这个题要求的是每一个格子参与交换的次数,针对一次交换要使用两个格子的次数,咱们把格子拆成入点,原点和出点,分别计算,至于交换次数天然让费用承担。好了,这只是大致的思路,可是实际上这题还有不少坑点和很差理解的地方。

拆点更多的是一种工具,并且是网络流里必要和强大的工具。能不能想出拆点的方法从而跑出网络流,每每是解决网络流问题的关键。

5、枚举和二分

原先这个版块叫枚举而二分最大流量,如今想一想,当时的理解仍是不够深入,实际上,可以枚举和二分的远远不止流量。这里详细从新讲述一下。

1.枚举和二分流量条件

固然,这个部分依然是必不可少的。

有这样一部分题目,它给你的图不必定是完整的,每每须要你肯定一个值来肯定是否可以跑出指望的最大流。就好比说去计算一个最少的时间或者花费时,咱们并无一个具体的数值,这个时候咱们每每预先经过链接inf先跑一遍最大流,以后二分时间每次重构图,再次跑最大流,而且经过此时的流量是否和刚才相等来调整二分的上下界。抑或跟人数有关的题目,咱们二分的时候判断最大流是否等于当前人数。

而后枚举的建图方法就更是简单,每次枚举+1时重构图,而后一直跑到不能再跑了为止。

2.枚举和二分费用

费用流的题目中,有些题目会给你费用的限制,或者说是间接的控制了你的流量,好比说他要一个知足条件时可能的最小费用,你仍然能够像刚才二分流量时那么跑。可是它也有可能要一个不超过一个费用时能知足的最大条件,这个时候去二分那个条件,而后控制费用。

3.枚举和二分边和点

有的题目里,各个点的添加是有顺序的,每一个点的边的添加也多是有顺序的,这个时候就有一类问题会让你肯定一个值,要这个值以前的全部点能够知足最大流量,这个时候咱们就要二分加入图中的点了,边也是同理。不过总的来讲,二分的方法万变不离其宗,老是要经过流量或者费用调整二分上下界。

6、点的构造

大部分题目是有迹可循的,由于他们至少有点或者有明显可以当作点的状态。或者题目给出的点就真的可以当成点使用。可是也有的题目,并无明显的点的提示,或者给出的你明显的点彻底不可以当成网络流里的点使用。对于这种题目,咱们就要本身构造出来一些新的点来进行网络流的使用。
单说可能不是很好理解,咱们放上几道题给你们看一下。

[HEOI2016/TJOI2016]游戏

这道题目大意就是让咱们在一个矩阵里放东西,若是没有硬石头限制,就是同行同列只能放一个。而后考虑算法,n,m<=50很明显这题与什么数据结构啊什么的是没有缘分了。那么就是DP或者网络流了。。考虑DP明显的状态太多根本无法转移,那么网络流呢?这一个一个点彻底没有什么用处连起来也无法限制。。。这时候咱们就要对这些已经有的点进行重构,根据网络流可以描述互斥关系的原理来搞出这道题。
那么:
这张网格图能够抽象成一系列行块和列块,列块和行块就是说,这一个横行块或纵列块里至多放一个炸弹,另外显而易见的,咱们发现选中一个点的时候会同时选中两个块,那么也就是说这张图知足两个限制条件
1.每一个点最多被选中一次
2.某些点对之间有必选关系
那么以后连边跑网络流便可了。

BZOJ4950:[Wf2017]Mission Improbable

先说一下题目大意,就是有一个网格方阵,每一个格子里都有一个权值,题目要求你在保证每行每列最大的权值不变的状况下,取走尽可能多的权值。而且原本有权的格子里不能取成0。

不取成0不用考虑,留下1就能够了。那么剩下的就是保证每行每列最大值不变了。咱们能够贪心的考虑每行都把那个最大的留下,若是有那么一行一列的箱子的最大值是相同的,这个时候把最大值放在交点处很明显就要比放在其它的地方要好的多。那么怎么处理这种状况呢?能够像上面的题目那样把每行每列都构形成点,而后一旦出现了刚才那种点,就连起来。以后跑一遍最大流,就能够求出来咱们可以放在交点出的最大值是多少了。

7、动态思想

1.基本介绍

有一些题目是这个样子的,他们每每有着较其余网络流题目更大的数据,而且他们也有一个特色,就是在一部分边连好以后,以后是先建建图或者是跑一个点再加一个点这么建图对最终结果并无影响。这样一来咱们就能够经过动态加点或者边的方式使其知足题目要求的复杂度。

2.限制条件

2.1数据范围不能过大

虽然说是减少了时间复杂度,可是也只是减小了部分,不少状况下复杂度的等级并无变化,只是由于在大部分操做里减小了一些没必要要的操做使速度加快。

2.2主要应用于费用流

大多时候动态加点的题目都是费用流题目,缘由就是最短路一次基本上也就只能增广出来一条增广路,若是是网络流的话,当前弧优化每每可以取到更好的效果。费用流因为大部分人都会使用EK的朴素算法,因此动态加点能够是速度提升好几个档次。

2.3当前最优原则

刚才说过了前面点的选择不会影响后面的选择,这里仔细说一下。由于在跑网络流的时候,不可避免的后面跑的点回合前面跑的点有冲突,这个时候咱们能够经过反边来使冲突化解。那么若是咱们一个一个的加点,很明显就没办法知足这个条件。可是若是咱们能够肯定当前这个点跑了这个流,后面的点跑这个流的必定不如这个点优,那么咱们就能够无视那个冲突了。

3.经典的例子

对于这个思想有一个很经典的题目,更好的是它甚至有数据弱化版来对比。

[NOI2012]美食节&&[SCOI2007]修车

这两个题的原理都是同样的,就是差在了一个数据范围,咱们先说后面那个数据范围小的。

题目描述

同一时刻有N位车主带着他们的爱车来到了汽车维修中心。维修中心共有M位技术人员,不一样的技术人员对不一样的车进行维修所用的时间是不一样的。如今须要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。

说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。

数据范围

(2<=M<=9,1<=N<=60), (1<=T<=1000)

Solution

要求平均时间最短,就等同于要求总时间最短

对于一个修车工前后用\(W_1-W_n\)的几我的,花费的总时间是

\[W_n\times 1+W_{n-1} \times 2+...+W_1 \times n\]

不难发现倒数第a个修就对总时间产生a*原时间的贡献

而后咱们将每一个工人划分红N个阶段,(i,t)表示修车工i在倒数第t个修

能够建一个二分图,左边表示要修理的东西,右边表示工人+阶段

因而能够从左边的e向右边的(i,t)连边,权值是\(Time[e][i]*t\),就是第e个用i这个修车工所用时间

最小权值彻底匹配后,最小权值和除以N就是答案

由于权值是正的,因此一个修车工接到的连线必定是从(i,1)开始连续的,也符合现实状况

由于假设是断续的,那后面的(i,n)改连向(i,n-k),k<n时,答案更优,违背了前面的最优性

这样咱们创建好图之后直接跑费用流就能够了。

那么咱们继续看下一道题

题目描述 2.0

m个厨师都会制做所有的n种菜品,但对于同一菜品,不一样厨师的制做时间未必相同。他将菜品用1, 2, ..., n依次编号,厨师用1, 2, ..., m依次编号,将第j个厨师制做第i种菜品的时间记为 ti,j 。小M认为:每一个同窗的等待时间为全部厨师开始作菜起,到本身那份菜品完成为止的时间总长度。换句话说,若是一个同窗点的菜是某个厨师作的第k道菜,则他的等待时间就是这个厨师制做前k道菜的时间之和。而总等待时间为全部同窗的等待时间之和。如今,小M找到了全部同窗的点菜信息: 有 pi 个同窗点了第i种菜品(i=1, 2, ..., n)。他想知道的是最小的总等待时间是多少。

数据范围 2.0

对于100%的数据,n <= 40, m <= 100, p <= 800, ti,j <= 1000 (其中p = ∑pi)

Solution 2.0

连边仍是和上一个题目同样。那么数据范围变大了,咱们如今的时间复杂度就是O(\(n^2m\times p \times \overline k\))嗯,T了。

那么怎么办呢?经过仔细的思考能够发现,咱们观察发现,第一次spfa得出的最短路确定是某人倒数第一个修某车某厨师倒数第一个作某菜,由于倒数第一个确定比倒数第二个距离短

那么咱们能够在一开始建图的时候,只把全部“倒数第一个作的菜”的那些边加上

一旦一条增广路被用掉了(也就是一个厨师-作菜顺序二元组(j,k) 被用掉了),那么咱们就把全部表明二元组(j,k+1) 加上去(一共有n条),再跑spfa

这样咱们图中的总边数不会超过$n\ast\sum_{i=1}^n p \lbrack i\rbrack $

也就是总时间在\(O\left(np^2\ast \overline k\right)\)左右,k是spfa常数,就能够经过这道题。

8、二分图

二分图又称做二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,若是顶点V可分割为两个互不相交的子集(A,B),而且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不一样的顶点集(i in A,j in B),则称图G为一个二分图。

好了不介绍这东西的基本定义了随便百度百度就应该能百度到了,至于增广路什么的也都去看看基础教程吧,笔者这里说的是网络流的建模。。

1.最大匹配

众所周知(?),二分图的最大匹配就是在二分图上跑出来的最大流。那么最优匹配什么的也能够网络流搞搞搞出来,并且时间复杂度通常都要比想象的好不少,大部分比匈牙利要快。。。

2.基本定义

为了方便待会理解,这里特别放上几个基本的定义。

最小覆盖:即在全部顶点中选择最少的顶点来覆盖全部的边。

最大匹配:二分图左右两个点集中,选择有边相连的两个匹配成一对(每一个点只能匹配一次),所能达到的最大匹配数。

独立集:集合中的任何两个点都不直接相连。

3.最小覆盖数=最大匹配数

这个比较基础,证起来主要看本身理解吧,具体的证实我也没有特别好的思路,不过仍是尽可能给你们证一下吧。咱们先搞一个二分图,而后最大匹配一下,如图,绿点就是匹配成功的点,红点就是匹配失败的点。那么咱们能够发现,每一条边都有两种状况,一种是连了一个红点一个绿点,一种是连了两个绿点。能够发现,没有任何一条边会链接两个红点,因此咱们在选择覆盖点的时候都选绿点。

img

而后又由于若是一个点链接了一个红点,那么与这个点相连的其余的绿点都必定不会链接红点,本身能够画一画,明显能够发现若是链接红点的话,那么刚才所求的就不是最大匹配了。那么对于这些点,咱们确定选择那个链接了红点的点做为覆盖点,由于并不会有其余的点与那个红点相连了。因此能够基本上的出最小覆盖数=最大匹配。

那么咱们就把这类问题转化成了网络流了。

4. 最大独立集=总点数-最小覆盖集

这个定理很是好用,很多题目都直接间接的用到了这个东西。先上图:

img

是否是很熟悉,没错就是刚才那个图,笔者实在懒得画另外一个了。咱们用那两个绿点完成了对这个图的最小覆盖。

而后咱们来反证这个结论,即证最小覆盖集+最大独立集\(\neq\)总点数。

首先咱们能够看出除了咱们选择的两个用来最小覆盖的点以外,剩下的点之间彼此之间都没有连边,咱们能够尝试把任意两个红点之间连一条边,那么明显,咱们不知足最小覆盖的要求了,或者咱们尝试经过转换使最小覆盖更小。。固然不可行,由于咱们已经求得就是最小覆盖了,而且易证的是剩下的全部点必定构成一个独立集。而且这个独立集的大小不可以更大了,而后咱们就证出了题目所给的定理。

5.删边

题目不适合起太长,这一部分主要是说:在一个二分图中,若是删去一条边可以使这个图的最大匹配减少1的话,那么这条边必定在残量网络中满流,而且它所链接的两个点必定不在同一个强连通份量当中。

首先满流的条件必定要有,不满流那这条边就不在最大匹配里了。而后就是不在同一个强连通份量里这个条件,咱们能够发现,在同一个强连通份量里的话,那他们就能够本身在这个强连通份量的内部进行增广,也就是说咱们能够经过操做从而找到另外一组最大匹配而且不须要使用当前这条边,因此这条边删掉也就无可厚非了。

6.最小路径覆盖

说一下大意,就是如今有一个有向无环图G,要求用尽可能少的不相交的简单路径覆盖全部的节点 。

那么这个也有一个结论,就是最小路径覆盖=原图节点数-最大匹配

诶,等等,哪里来的最大匹配。。

咱们对当前的图拆点,把每一个节点都拆成x,y两个节点。若是\(x_1有一条指向x_2的边,那么就把x_1和y_2之间连一条边\),这样咱们就能获得一个二分图了,那么为何这么作可行呢?

一开始每一个点都是独立的为一条路径,总共有n条不相交路径。咱们每次在二分图里找一条匹配边就至关于把两条路径合成了一条路径,也就至关于路径数减小了1。因此找到了几条匹配边,路径数就减小了多少。因此有最小路径覆盖=原图的结点数-新图的最大匹配数。

以上就是咱们经常使用的网络流构图的基本思想了,接下来笔者会从作过的题目里挑选出来一些以为建模比较有表明性的题目分享给你们

9、经典的建模

其实你也能够看出来笔者这个地方写的很青涩,那是早以前写的了,可能对于刚刚入门网络流的人比较有用,可是对于大佬们确定用处不大了,下面的题目确定也都有本身独特的看法和理解方式,因此请dalao从6开始看。。。

1.方格取数增强版

注意,这道题和刚才那个不是一道题目。

记不记得咱们NOIP曾经考过一道叫作方格取数题目。这道题就是那个题的增强版。原本是要走2次,如今变成了要走n次。。原来的DP方案好像一会儿就垮掉了。如今咱们来这么考虑,咱们把这个题当成一个图论题而不是DP题,那么咱们走过的格子就要拿走相应格子的点权,刚才说过什么,若是是点权问题的话,是否是要拆点。那么咱们又怎么处理每一个格子能够走无数次呢?好办,咱们对于每一个格子的入点和出点分别连两条边,一条是费用为点权流量为1的边,表示能够取走这个格子里的点权;一条是费用为0流量为inf的边,表示这个格子能够无数次通过。而后相应的创建超级源和超级汇,分别向左上角和右下角链接流量为k的边来限制要走k次这个条件。这样咱们是否是就作出了这个题。

2.[CQOI2015]网络吞吐量

这道题就开始慢慢的使建图复杂起来了,咱们根据题意能够发现这道题的网络流是严格的按照最短路跑的。等等?按照最短路跑?这和树上跑网络流有个P区别,不直接写出来那个最小的流量O(1)出解更好么?而后笔者就画了画样例模拟了一下,发现这个样例,并不仅有一条最短路!!!因此咱们先求出来一遍最短路以后,经过遍历一个点的全部出边的方式断定到下一个点的是否是最短路。若是$ dis[v]=dis[u]+edge[i].dis $那么咱们就找到了到下一个点的一条最短路。而后连边求最短路便可。

3.数字梯形问题

这个题有三问,虽然很类似就是了。让咱们一问一问解决这个题目。P.s.把这个题放在这里的意义就是让你们思考一下网络流里链接inf边的道理。

首先先从起点到最上层的每一个数字连流量为1的边,表示从这些数字开始走,而后从最下层的每一个数字连到汇点,流量为1。

①从梯形的顶至底的 m 条路径互不相交;

这一问就是每一个点和每条边都只能用一次的意思,因此练出来流量为1的边跑费用流就能够了。

②从梯形的顶至底的 m 条路径仅在数字结点处相交;

那么咱们如今就能够使用重复的点了对不对。。。因此入点和出点的连边咱们就能够连inf了,表示这个点能够用无数次。而后咱们再跑一遍费用流。

③从梯形的顶至底的 m 条路径容许在数字结点相交或边相交。

话说。。边怎么相交,这个题貌似相交不了。。实际上就是能够通过重复的边,而后咱们每一个点的出点连向下个点的入点的流量也设置为inf而后咱们再跑费用流。

好了,看上去这道题A了,那么交上去试试?而后你就会有这种效果:

为何,就是由于这一句话 “而后从最下层的每一个数字连到汇点,流量为1。”,若是咱们这么连边了,那么最后一层的点,就只能使用一次,而后你不就挂掉了。。。因此从第二次开始,咱们就要把这条边连inf变成1,而后就能够了。

4. 餐巾计划问题

网络流24题里仍是有很好的题目的,好比说这一道,建图方式妥妥的一股清流。首先咱们发现这个题的状态不少,首先是两个洗毛巾的操做,这个比较好办,拆点了以后直接去洗就能够了,而后就是这个题目的精髓所在,对于这种直接购买进来的操做,咱们往常的思路是向入点连边,而后限制流量,然而这个题咱们仅仅只在入点限制流量,而把买餐巾这个操做连到当天的出点上,这样就能够使在洗掉的餐巾不足以支持满流的状况下是这一天满流。而后由于咱们有攒着餐巾不使用这种操做,因此咱们要把上一天的入点和下一天的入点之间连一条inf的边表示这种状况。

5.[CTSC1999]家园

这个题具体来讲就是一个拆门的操做(雾),先让笔者来讲明一下拆门这个操做是什么含义吧。。

笔者有次作了一道叫作紧急疏散的网络流题目,一开始笔者是用时间点建分层图,结果发现不能够这么作,整个题目会爆炸掉,可是这个题又没有更好的方法去支持时间的限制,而后笔者发现能够有拆门这种操做,由于门,也就是指定的汇点的个数老是有限的,哪怕你把这东西拆成500个点,也不会形成有太多边的状况。而后从天天向下一天连边,就能够在一些题目上避免使用分层图形成复杂度太高。

那么这个题咱们也能够这么干。首先咱们把天天的地球和月球都和超级源点和汇点连inf的边,而后把太空船上一天的位置和这一天的位置连一条容量为太空船装载人数的边,而后再从每一天的向下一天连一条inf边表示能够用人留在这里,咱们就能够经过每一天的最大流跑出结果。而后是一个操做,大部分这种题目咱们要二分答案,然而二分的代价就是你要有很是繁杂的预处理过程才能保证你建出来的图没有问题,然而这个题的数据范围小到让你能够直接枚举,而且每次不把图清零,而是在已有的残量图上继续跑,从而达到不错的时间效率与较低的代码难度。


从这里开始这一个专题要变得不和谐起来了,而且可能有我本身YY的神奇的模型。

6.黑白染色

怎么说呢,黑白染色这东西,很迷。

6.1适用性

黑白染色必定是在棋盘图上,嗯,这点绝对没有问题。而后就是要把棋盘的格子当作点,而且通过了染色以后,你会惊奇的发现黑点只会限制白点,不会限制黑点,白点也只会限制黑点。也就是说同色的点之间并无直接关系。

6.2为何使用它

当你拿到一个棋盘的题手足无措,就要被水淹没时,黑白染色颇有可能救你一命。黑白染色,一共只有两种颜色,因此这样建图以后通常都会和二分图很是像,而后就是随便匹配的事了。另外一种状况是你把这题转化成了一个最小割的模型,发现题目中删去某个点或者添加某个点的操做就是在删边。从而随便网络流跑出来这个题。

6.3模型创建

基本上能够肯定的是你选择一个你喜欢的颜色,把它连向你不喜欢的那个颜色,而后你不喜欢的那个颜色去连汇点,你喜欢的连源点。可是问题也就来了,这个东西适用于几乎全部要用到黑白染色的题目,那么也就是说,它没屁用。实际上,真正恶心的通常都在黑白点互相链接的限制上,因此具体问题只能具体分析。

6.4Difficult

然而有的时候发现黑白染色并不能彻底解决问题,或者说这个题并不能符合黑白染色的定义。。那么咱们就红黄蓝染色,再不行就红黄绿蓝染色。而后从新构造模型。不得不说,这东西有的时候仍是能派上大用的。

6.5时间复杂度

黑白染色的题目,咱们必定要相信信仰,由于建出来的图基本上都是那种二分图,仍是边比较稀疏的那种,因此咱们能够几乎在\(O(n^{1.5})\)的时间复杂度就解决问题,因此不要问为何有的题1e5的数据都跑网络流了。

7.切糕模型

事先证实,这个鬼畜的名字不是我叫出来的,是网上某个博主这么叫的,而后我一想确实是颇有道理。这种模型原本应该叫距离限制模型。也就是说,每一个元素选择时,有多种选择,而且相邻两个元素之间的选择会相互限制。例题就是HNOI某一年的一道叫切糕的题目。

其实这个模型挺经典的,可是不少状况下题目不只仅只是有这样一个限制,而后就被其它的模型覆盖了。

8.最大密度子图

这个事实上均可以单独拿来当成一个题作了。先说一下要求:

给定一个无向图,要求从无向图里抽出一个子图,使得子图中的边数\(\mid E\mid\)与点数\(\mid V\mid\)的比值最大,即求最大的\(\frac{\mid E\mid}{\mid V\mid}\).

给出一种解法,由前面说过的最大权闭合子图获得的。假设答案为k ,则要求解的问题是:选出一个合适的点集 V 和边集 E,令(|E|−k∗|V|)取得最大值。所谓“合适”是指知足以下限制:若选择某条边,则必选择其两端点。

建图:以原图的边做为左侧顶点,权值为1;原图的点做为右侧顶点,权值为 −k (至关于 支出 \(k\))。  若原图中存在边 (u,v),则新图中添加两条边 ([uv]−>u), ([uv]−>v),转换为最大权闭合子图。 其中k能够二分获得。


锅,忽然发现这个东西没证。。。。

而后发现第一步并不能证出来。。。

如今我又证出来了。。。

无向图的子图的密度:\(D=\frac{|E|}{|V|}\)

当作是分数规划问题,二分答案 λ ,须要求

\(g(\lambda)=max(|E|-\lambda|V|)\)

g(λ)=0时咱们取到最优解。

喏,如今权当我这一步证出来了。

给出证实吧。

给出函数\(\lambda=f(x)=\frac{a(x)}{b(x)}(x\in S)\)

那么咱们猜想一个最优值λ,从新构造一个函数:

\(g(\lambda)=max\lbrace a(x)-\lambda b(x)\rbrace\)

若咱们设一个\(\lambda^*=f(x^*)\)为最优解,那么则有:

\(g(\lambda)<0⇔\lambda>\lambda^*\)

\(g(\lambda)=0⇔ \lambda=\lambda^*\)

\(g(\lambda)>0⇔ \lambda<\lambda^*\)

彷佛貌似就是大于小于的状况要么不合法,要么不够优。当g(λ)=0时正好取到最优解。

而后咱们开始思考如何搞出来,能够发现咱们选择一条边就要选择与它相连的两个端点。那么,随便YY一下,搞一个如上面的建图,就能够发现上面那个建图知足咱们的条件。

但是有个问题,当λ取到必定值的时候,它变大并不会影响最大权闭合图的值,通通都是0.。因此咱们二分求得是在g(λ)=0时最小的λ。

9.疏散模型

这个模型就是我本身YY出来的了。前面应该提到过了,我作过一道叫紧急疏散的鬼畜题。这道题让我生生调了半天代码,今后对这题印象深入。

那么这是一个什么样的模型呢?它利用两个特色,一个是分层图的一些概念,一个是先后缀的思想。明显的是,分层图咱们可能在一些题目中看出,而后坑爹的是,这个题分层的话整个题目复杂度极高,时间空间都不支持。这个时候能够尝试只拆开汇点,也就是我以前所说的拆门。而后对于那个时间点能到汇点的点,与这个时刻的门连一条边。而后利用前缀思想,把每一天的汇点一串inf连下去,最后就能够求出。并且时空复杂度都很小啊。

10.优化建图

有这么些题,你一看就知道应该怎么建图,而后仔细看看数据范围,用常规方法不是炸你空间就是让你T掉。。。。这个时候咱们能够尝试一下优化,好比说使用数据结构。虽然这东西放出来以后通常人不会愿意打。那么是什么原理呢?若是咱们某个区间里的每一个点都要互相连边,那么就能够建线段树,而后把对应的区间连边,而后线段树的节点之间互相连边,就能够优化边数。

然而值得注意的是,我并无写数据结构/优化建图,由于更不正常的题目,咱们或许会用到计算几何。十分鬼畜,鬼畜至极。。然而这种题目。。鬼才会去写啊。。。有兴趣的去看看[Jsoi2018]绝地反击 。话说这个优化的原理是什么呢?举个栗子:假设一个题目有不少点,可是你通过各类手玩和证实以后,发现全部不在凸包上的点不会互相连边,这个时候求一发凸包,就能够大大的减小边数了。

11.最长反链和最小链覆盖

有向无环图中,链是一个点的集合,这个集合中任意两个元素v、u,要么v能走到u,要么u能走到v。

反链是一个点的集合,这个集合中任意两点谁也不能走到谁。

最长反链是反链中最长的那个。

那怎么求呢?

11.1 传递闭包

在开始介绍这个以前,我先简单叙述一下传递闭包是个什么东西。能够发现的是百度百科里的东西都奇怪的一批根本让人看不懂。那么我就尽可能通俗的说一下。

传递闭包就是求一个图里面全部知足传递性的点。那么传递性又是什么呢?简单来讲就是:假设图中对于图中的节点\(i\),若果有一个j点能够到\(i\)\(i\)点又能够到\(k\),那么\(j\)就能到\(k\)。这样的节点就能够说是有传递性。看上去很是的,简单。其实就是这样。求完传递闭包以后,咱们可以知道的是图中的任意两点是否相连。

那么,咱们能够用Floyd算法搞出来,没错,就是Floyd。只不过咱们如今是求两点是否相连,而不是求其最短路径。

11.2最小链覆盖

这个东西就是求出最长反链的关键。由于:最小链覆盖=最长反链长度。

那么,最小链覆盖又怎么求出呢?

回想一下咱们以前是否是有一个地方介绍过了最小路径覆盖。那个地方所求的是不能相交的最小路径覆盖,那么这里咱们要求的就是路径能够相交的最小路径覆盖。

问题又来了,这个又怎么求解呢?

咱们把原来的图作一遍传递闭包,而后若是任意两点\(x,y\),知足\(x\)能够到达\(y\),那么咱们就在拆点后的二分图里面连一条\((x_1,y_2)\)的边。

这样球能够绕过一些在原图中被其余路径占用的点,构造新的路径从而求出最小路径覆盖。

那么这样咱们就把能够相交的最小路径覆盖转化为了路径不能相交的最小路径覆盖了。 从而咱们求出了这个图的最小链覆盖。

11.3 十分感性的证实

那么咱们证实一下刚才一个结论的正确性。

咱们经过观察定义能够得出,最长反链的点必定在不一样的链里。因此最长反链长度\(\le\)最小链覆盖数。

那么咱们接着能够经过感性理解和数学概括证出最长反链长度\(\ge\)最小链覆盖数.

如此咱们就可以得证了。

11.4最长链长度 = 最小反链覆盖数

不想证了。这个结论就是上面的反向结论,其实直接背过就能够了。。。。。

12.混合图的欧拉回路

12.1欧拉回路

(1)给定一个图G,假设咱们能一笔画,若是图G中的一个路径包括每一个边刚好一次,则该路径称为欧拉路径(Euler path)。若是一个回路是欧拉路径,则称为欧拉回路(Euler circuit)。
具备欧拉回路的图称为欧拉图(简称E图)。具备欧拉路径但不具备欧拉回路的图称为半欧拉图。

(2)对于一个无向图,若是每一个点的度数均为偶数,那么这个图是一个欧拉图。

(3)对于一个有向图,若是每一个点的出度都等于入度,那么这个图是一个欧拉图。

(4)对于无向图,若是每一个点度数均为偶数,或者有且仅有两个顶点度数为奇数,那么这个图中存在欧拉路径。

(5)对于有向图,若是每一个点的出度等于入度,或者有且仅有两个点不符,且这两个点一个入度比出度小1,一个出度比入度小1,则这个图存在欧拉路径。

12.2求解通常欧拉图

直接暴力枚举全部点的出度入度便可,用上面的关系进行判断,能够求出。

12.3求解混合欧拉图

明显,不能经过刚刚的方法来套用到这个题上去。

对于这种图,咱们首先能够自定向无向边的方向。能够rand,也能够规定一个方向从而所有指向一个方向。不过这样没法直接获得答案,说不定脸好,可是咱们能够经过这样一个操做搞出来一个彻底有向的图,能够套用上面的性质来初步判断这个题是否有欧拉回路。不过这样作明显没有依据,只是一个预处理的过程,再经过调整判断是否有解。

那么如何自调整呢?引用一段话:

所谓的自调整方法就是将其中的一些边的方向调整回来,使全部的点的出度等于入度。可是有一条边的方向改变后,可能会改变一个点的出度的同时改变另外一个点的入度,至关于一条边制约着两个点。同时有些点的出度大于入度,迫切但愿它的某些点出边转向;而有些点的入度大于出度,迫切但愿它的某些入边转向。这两条边虽然需求不一样,可是他们之间每每一条边转向就能同时知足两者。

而后咱们发现,网络流貌似能够知足这个自调整的性质。

12.4算法步骤

1.\(首先对于每一个入度>出度的点,咱们将其与源点S链接一条权值为\frac{入度-出度}{2}的边;\)

\(对于每一个入度<出度的点,咱们将其与汇点T链接一条权值为\frac{出度-入度}{2}的边。\)

为何除以2,由于咱们改变一条边的方向时,会形成出度和入度的改变,因此。。。

2.而后将原图中全部你定了向的无向边链接的两个点连一条边权为1,方向为你定向方向的无向边。

3.跑网络流去,若是满流天然就有解。

4.把在网络流中那些由于原图无向边而建的流量为1的边中通过流量的边反向,就造成了一个能跑出欧拉回路的有向图,若是要求方案,用有向图求欧拉回路的方法求解便可 。

13.最大权闭合子图

发现前面写最小割的时候把这个东西跳过去了,。。。如今补上。

13.1定义

一个子图(点集), 若是它的全部的出边所指向的点都在这个子图当中,那么它就是闭合子图。  点权和最大的闭合子图就是最大闭合子图。

13.2构图

设置超级源汇S,T。

而后使S和全部的正权的点链接权值为点权的边,全部点权为负的点和T链接权值为点权绝对值的边。而后若是选择了某个v点才能够选u点,那么把u向v链接一条权值为\(\infty\)的边。

而后随便跑跑网络流。

最大点权和=正点权和-最小割。

13.3并不感性的证实

首先说简单割,就是咱们求最小割的时候每条割边都与S或T相连。在闭合图中,因为咱们不与S或T链接的边的权值都是inf,因此咱们不会割到他们。容易得证闭合图中的最小割是简单割。

接着证实简单割和闭合图的关系。

由于简单割不会含有那些正无穷的边,因此不含有连向另外一个集合(除T)的点,因此其出边的终点都在简单割中,知足闭合图定义。

咱们假设跑完最小割以后的图中一共有两部分点\(X,Y\)\(X\)表示与S相连的点,\(Y\)表示与T相连的点,那么咱们能够设\(X_0,Y_0\)为负权点,\(X_1,Y_1\)为正权点。而后因为咱们求了一遍最小割,根据上面简单割的证实咱们发现不会割去中间inf的边,因此咱们能够求出:

\(最小割=|X_0|+|Y_1|\)

刚才已经证实过X,Y均是闭合图。

而后咱们能够发现X的权值和能够表示为\(Sum_X=X_1-|X_0|\)

咱们把上面那个式子代入到这个式子里。

\(Sum_X+最小割=X_1-|X_0|+|X_0|+Y_1\)

而后咱们得出:

\(Sum_X+最小割=X_1+Y_1=原图全部正权点权值之和\)

而后咱们得证。

14.文理分科模型

原本不想整理这个,由于确实不是特别的突出,用的地方也不是不少。而后,笔者在写这个东西的这一天连着看见两道这样的题。因此仍是整理一下。

这里表示若是点的贡献不只仅是点权,并且还有附加的条件的话,好比同时选A,同时选B,选择不相同。。。。。

那么咱们就利用最小割的性质构造一下便可。用“割掉”表明选择。

而后分状况讨论全部选择状况,使对应状况下被割掉的边流量之和等于权值 便可。


到这里这个课件的系统知识部分就已经结束了,然而仍是有不小的漏洞,好比线性规划,好比有上下界的网络流,好比流量平衡等等。不过笔者自信本身的绝对是目前网上能找到的最全的一个了。

10、几道脑洞大开的题目

这些题大多比较新,都是这两年的新题,而且建模的思路极其鬼畜新颖,因此仍是很值得一看的。不,是很值得一看,并且我在这里写的题解确实,十分详细。。。。


1.[SDOI2017]新生舞会

题面描述

学校组织了一次新生舞会,Cathy做为经验丰富的老学姐,负责为同窗们安排舞伴。

有nn 个男生和nn 个女生参加舞会买一个男生和一个女生一块儿跳舞,互为舞伴。

Cathy收集了这些同窗之间的关系,好比两我的以前认识没计算得出 \(a_{i,j}\)

Cathy还须要考虑两我的一块儿跳舞是否方便,好比身高体重差异会不会太大,计算得出 \(b_{i,j}\) ,表示第i个男生和第j个女生一块儿跳舞时的不协调程度。

固然,还须要考虑不少其余问题。

Cathy想先用一个程序经过\(a_{i,j}\)\(b_{i,j}\) 求出一种方案,再手动对方案进行微调。

Cathy找到你,但愿你帮她写那个程序。

一个方案中有n对舞伴,假设没对舞伴的喜悦程度分别是\(a'_1,a'_2,...,a'_n\),假设每对舞伴的不协调程度分别是\(b'_1,b'_2,...,b'_n\) 。令

C=\(\frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}′\)

Cathy但愿C值最大。

数据范围

对于100%的数据,\(1\le n\le 100,1\le a_{i,j},b_{i,j}<=10^4\)

Solution

这个题乍一看上去非常让人一筹莫展,DP明显被强大的后效性D掉了,听说有线性规划的作法,可是笔者并无想出来,搜索的话n=100让人非常头疼。那么如今没有什么办法,咱们就只能再回到题目里面去看看有没有什么没有发现的信息。

而后咱们发现了这个东西:C=\(\frac{a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}′\)

咱们发现这个分式必定有意义,那么分母不可能为0,又由于给的都是正整数,因此分母大于0。

那么首先咱们先去分母,这个式子就变成了:\((b'_1+b'_2+\cdots+d'_n)\times C=(a'_1+a'_2+\cdots+a'_n)\)

而后咱们移项,它就成了\((a'_1+a'_2+\cdots+a'_n)-(b'_1+b'_2+\cdots+d'_n)\times C=0\)

而后咱们去括号,就变成了:\(a'_1+a'_2+\cdots+a'_n-b'_1\times C-b'_2\times C-\cdots-b'_n\times C=0\)

为了方便计算,咱们能够合并一下同类项:\((a'_1-b'_1\times C)+(a'_2-b'_2\times C)+\cdots+(a'_n-b'_n\times C)=0\)

咱们题目中要求的式子就和如今的式子同样了。

此时咱们就能够二分C值,而后判断tot是否大于或小于0来缩小边界。

而后咱们发现每一个{a,b}数对的下标都是一一对应的,因此咱们直接从第i个男生想第j个女生链接一条\(a_i-b_i\times C\)的边就能够了。

说了从这里开始就会有代码。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 1000000000000001ll
#define maxxx 500000001
#define re register
#define ll long long
#define min(a,b) a<b?a:b 
using namespace std;
const long double eps=0.00000007;
struct po{
    int to,nxt,w;
    ll dis;
};
po edge[800001];
int n,m,s,t,b[205],p;
int head[205],num=-1;
ll tot,dis[205],pa[501][501],pb[501][501];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w,ll dis)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    edge[num].dis=dis;
    head[from]=num;
}
inline void add(int from,int to,int w,ll dis)
{
    add_edge(from,to,w,dis);
    add_edge(to,from,0,-dis);
}
inline bool spfa()
{
    for(re int i=s;i<=t;i++) dis[i]=inf+1;
    memset(b,0,sizeof(b));
    queue<int> q;
    q.push(t);
    dis[t]=0;
    b[t]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        b[u]=0;
        for(re int i=head[u];i!=-1;i=edge[i].nxt){
            int v=edge[i].to;
            if(edge[i^1].w>0&&dis[v]>dis[u]-edge[i].dis){
                dis[v]=dis[u]-edge[i].dis;
                if(!b[v]){
                    b[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return dis[s]<inf;
}
inline int dfs(int u,int low)
{
    b[u]=1;
    if(u==t) return low;
    int diss=0;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(edge[i].w&&!b[v]&&dis[v]==dis[u]-edge[i].dis){
            int check=dfs(v,min(edge[i].w,low));
            if(check){
                tot+=check*edge[i].dis;
                low-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(low==0) break;
            }
        }
    }
    return diss;
}
inline void max_flow()
{
    int ans=0;
    while(spfa()){
        b[t]=1;
        while(b[t]){
            memset(b,0,sizeof(b));
            ans+=dfs(s,maxxx);

        }
    }
    return;
}
inline void build(ll x)
{
    memset(head,-1,sizeof(head));
    num=-1;tot=0;
    for(re int i=1;i<=n;i++)
    add(s,i,1,0),add(n+i,t,1,0);
    for(re int i=1;i<=n;i++)
     for(re int j=1;j<=n;j++)
     add(i,j+n,1,-(pa[i][j]-pb[i][j]*x));
}
inline bool check(ll x)
{
    build(x);
    max_flow();
    if(-tot<=0) return 1;
    else return 0;
}
int main()
{
    n=read();
    for(re int i=1;i<=n;i++) 
     for(re int j=1;j<=n;j++)
     pa[i][j]=read(),pa[i][j]*=5000000;
    for(re int i=1;i<=n;i++)
     for(re int j=1;j<=n;j++)
     pb[i][j]=read();
     s=0,t=n+n+1;
    ll l=1,r=50000000000ll;
    while(r>=l){
        ll mid=(l+r)/2;
        if(check(mid))
        r=mid-1; else
        l=mid+1;
    }
    printf("%.6lf",l*1.0/5000000);
}

这个题要用数组写费用流,卡告终构体。。。。

2.[2018多省省队联测]劈配

这是省选考试题了,惋惜当时并无想到最后。。。

题目描述

总共n名参赛选手(编号从1至n)每人写出一份代码并介绍本身的梦想。接着由全部导师对这些选手进行排名。

为了不后续的麻烦,规定不存在排名并列的状况。

同时,每名选手都将独立地填写一份志愿表,来对总共m位导师(编号从1至m)做出评价。

志愿表上包含了共m档志愿。

对于每一档志愿,选手被容许填写最多C位导师,每位导师最多被每位选手填写一次(放弃某些导师也是被容许的)。

在双方的工做都完成后,进行录取工做。

每位导师都有本身战队的人数上限,这意味着可能有部分选手的较高志愿、甚至是所有志愿没法获得知足。节目组对”

前i名的录取结果最优“做出以下定义:

前1名的录取结果最优,当且仅当第1名被其最高非空志愿录取(特别地,若是第1名没有填写志愿表,那么该选手出局)。

前i名的录取结果最优,当且仅当在前i-1名的录取结果最优的状况下:第i名被其理论可能的最高志愿录取

(特别地,若是第i名没有填写志愿表、或其全部志愿中的导师战队均已满员,那么该选手出局)。

若是一种方案知足‘‘前n名的录取结果最优’’,那么咱们能够简称这种方案是最优的。

举例而言,2位导师T老师、F老师的战队人数上限分别都是1人;2位选手Zayid、DuckD分列第一、2名。

那么下面3种志愿表及其对应的最优录取结果如表中所示:

img

能够证实,对于上面的志愿表,对应的方案都是惟一的最优录取结果。

每一个人都有一个本身的理想值si,表示第i位同窗但愿本身被第si或更高的志愿录取,若是没有,那么他就会很是沮丧。

如今,全部选手的志愿表和排名都已公示。巧合的是,每位选手的排名都刚好与它们的编号相同。

对于每一位选手,Zayid都想知道下面两个问题的答案:

在最优的录取方案中,他会被第几志愿录取。

在其余选手相对排名不变的状况下,至少上升多少名才能使得他不沮丧。

做为《中国新代码》的实力派代码手,Zayid固然轻松地解决了这个问题。

不过他仍是想请你再算一遍,来检验本身计算的正确性。

数据范围

img

Solution

这个题刚拿到题我就基本肯定网络流算法了。。你看这名字,劈配,多么的人性化,直接把算法告诉你。而后开始思考建模,最后崩溃。

那么这个题应该怎么作呢,咱们一步一步的分析:

2.1面向小范围数据

首先咱们能够发现有那么一部分数据的范围真是有够小的,那么咱们能够搜索,暴力查找出全部可能的方案,而后选取最优的一个,就能够先把1,2,3这三个数据点的分拿到手。

2.2针对数据特色

咱们能够发现这些数据总共有两个特色,一个是C的特殊性,一个是b的特殊性。

针对C的特殊性,咱们发现能够有一个贪心,就是最高的开始一个一个选,若是后面有不能选的,那么他必定就没人可选,而后暴力求解出第二问就能够很稳的拿到4,5,6,7这40分。

而后针对b的特殊性,嗯,有一个方法来着,动态加边的二分图匹配,这样又能够获得第9个点的10pts。

2.3 各类伪算法

而后就是各类奇奇怪怪的算法了,好比我考场上写的费用流,回来用mhr的思路写的拆点的网络流,还有什么极其暴力的复杂度很高的匈牙利什么的,它们都能过一部分点,可是全都不行。这里介绍一下mhr思路的那个作法,实际是可行的,只是我第二问作错了,他的第二问写的太丑T了而已。

很明显c=1的能够随便过去,那么C=其余 的呢?咱们考虑拆点,把每个人拆成总共志愿个数个点,把全部导师向汇点连边,全部的志愿都和导师连边。而后对每个人动态加边,就是这我的先和他的第一志愿连边,而后判断是否有流量增量,若是没有,就先把这个边删了,和下一个志愿连边,这样针对任何一我的,他前面是已经连好的最优的状况,而且因为边的限制,它不会影响前面的最优状况。这样跑到最后咱们就会获得一个最优解。

而且因为同一个时刻咱们这里面最多有nm条边,因此并不会有时间复杂度问题,这样第一问就作出来,而后考虑第二问,咱们枚举全部不满意的人,使用二分,二分这我的前进到多少名能够保证他可以知足要求。可是这样咱们每一次都要从新建图,时间复杂度大概是\(O(nlogn\times n^2m)\),看上去问题很严重,当初我和mhr是这么说的,你不用跑网络流,你看你那里有个memset,你光这么多遍memset就超时了。。。

可是咱们在通过一些剪枝优化以后,实际上的复杂度远远没有这么多,就能够在一个合理的时间范围内获得答案。而后估计能够是最优解最后一名了。。。

由于我第二问求错了,mhr写的太丑T了,因此这个方法就不放代码了。

2.4正解算法

在上一个mhr算法中,咱们能够发现每个人实际上只有一个利用的点,也就是说最后的图里面咱们一共有\((n-1)\times m\)这么多的点白白浪费掉了。因而能够考虑能不能不拆点,找到一些其余的方法来实现那个过程。而后发现刚才拆点的目的是为了在后面的人选的时候不让前面的人选到更差的。最后删边完善了这个操做。可是实际上咱们能够经过从新建图来达到一样的效果。能够发现的是第一问里面咱们是很是严格的按照排名选的,一旦一我的的志愿等级肯定了,那么就必定不会更改。而且这我的选的志愿等级咱们已经保存在了输出数组里面。每一次直接从新调用就能够了。能够发现虽然多了一个十分繁琐的从新建图的过程,可是并无将他的复杂度提高太多。

而后对于第二问,咱们依然枚举n而后二分,可是如今的是时间复杂度变成了\(O(nlogn\times nm)\)的,就能够跑过去了。

代码以下了:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#define re register
#define inf 50000001
#define ll long long
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
using namespace std;
struct po{
    int nxt,to,w;
};
po edge[6000001];
int T,C,n,m,s,t;
int head[501],dep[501],num=-1,b[205],want[202];
int a[205][205],rs[205][205],ql[202],ans,cur[501],last,tag;
int out1[201],out2[201];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w)
{
    edge[++num].nxt=head[from];
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
inline void add(int from,int to,int w)
{
    add_edge(from,to,w);
    add_edge(to,from,0);
}
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;
    while(!q.empty())
    q.pop();
    q.push(s);
    dep[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(re int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dep[v]==0&&edge[i].w>0)
            {
                dep[v]=dep[u]+1;
                if(v==t)
                return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int dfs(int u,int dis)
{
    if(u==t) return dis;
    int diss=0;
    for(re int& i=cur[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(edge[i].w!=0&&dep[v]==dep[u]+1){
            int check=dfs(v,min(dis,edge[i].w));
            if(check>0)
            {
                dis-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(dis==0) break;
            }
        }
    }
    return diss;
}
inline void dinic()
{
    while(bfs())
    {
        for(re int i=s;i<=t;i++)
            cur[i]=head[i];
        while(int d=dfs(s,inf)) ans+=d;
    }
}
void init(){
    memset(out1,0,sizeof(out1));
    memset(out2,0,sizeof(out2));
    n=read();m=read();
    s=0;t=m+n+1;
    for(re int i=1;i<=n;i++) out1[i]=m+1;
    for(re int i=1;i<=m;i++) b[i]=read();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            a[i][j]=read();        
    for(re int i=1;i<=n;i++) want[i]=read();
}
int main()
{
    //freopen("date.in","r",stdin);
    T=read();C=read();
    while(T--){
        init();
        for(re int i=1;i<=n;i++){
            num=-1;memset(head,-1,sizeof(head));
            for(re int j=1;j<=n;j++){if(j==i) tag=num+1;add(s,j,1);}
            for(re int j=1;j<=m;j++) add(n+j,t,b[j]);
            for(re int j=1;j<i;j++)
                for(re int l=1;l<=m;l++)
                    if(a[j][l]==out1[j]) add(j,n+l,1);
            dinic();
            for(re int l=1;l<=m;l++){
                for(re int j=1;j<=m;j++)
                    if(a[i][j]==l) add(i,n+j,1);
                dinic();
                if(edge[tag].w==0){out1[i]=l;break;}
            }
        }
        for(re int i=1;i<=n;i++){
            if(out1[i]<=want[i]) continue;
            int l=1,r=i-1;out2[i]=i;
            while(l<=r){
                num=-1;memset(head,-1,sizeof(head));
                for(re int j=1;j<=n;j++){if(j==i) tag=num+1;add(s,j,1);}
                for(re int j=1;j<=m;j++) add(n+j,t,b[j]);
                int mid=l+r>>1;
                for(re int j=1;j<mid;j++)
                    for(re int l=1;l<=m;l++)
                        if(a[j][l]==out1[j]) add(j,n+l,1);
                dinic();
                for(re int j=1;j<=m;j++)
                    if(a[i][j]>0&&a[i][j]<=want[i]) add(i,n+j,1);
                dinic();
                if(edge[tag].w==0) {l=mid+1;out2[i]=i-mid;}
                else r=mid-1;
            }
        }
        for(re int i=1;i<=n;i++)
        cout<<out1[i]<<" ";
        cout<<endl;
        for(re int i=1;i<=n;i++)
        cout<<out2[i]<<" ";
        cout<<endl;    
    }
}

3.[2017国家集训队测试]无限之环

题目描述

曾经有一款流行的游戏,叫作InfinityLoop,先来简单的介绍一下这个游戏:
游戏在一个n×m的网格状棋盘上进行,其中有些小方格中会有水管,水管可能在方格某些方向的边界的中点有接口
,全部水管的粗细都相同,因此若是两个相邻方格的公共边界的中点都有接头,那么能够看做这两个接头互相链接
。水管有如下15种形状:
img

游戏开始时,棋盘中水管可能存在漏水的地方。
形式化地:若是存在某个接头,没有和其它接头相链接,那么它就是一个漏水的地方。
玩家能够进行一种操做:选定一个含有非直线型水管的方格,将其中的水管绕方格中心顺时针或逆时针旋转90度。
直线型水管是指左图里中间一行的两种水管。
现给出一个初始局面,请问最少进行多少次操做能够使棋盘上不存在漏水的地方。

第一行两个正整数n,m表明网格的大小。
接下来n行每行m数,每一个数是[0,15]中的一个
你能够将其看做一个4位的二进制数,从低到高每一位分别表明初始局面中这个格子上、右、下、左方向上是否有水管接头。
特别地,若是这个数是000,则意味着这个位置没有水管。
好比3(0011(2))表明上和右有接头,也就是一个L型,而12(1100(2))表明下和左有接头,也就是将L型旋转180度

数据范围

\(n×m≤2000\)

Solution

说实话这个题真的很差作,我本身也是调了一个上午,代码量要好调的就很大,可是缩减代码长度的话错了又很差调。。很恶心的一个题。

3.1肯定算法

有谁能够一眼看出来这题网络流我真的服他,墙都不服就服他。这个题笔者仔细揣摩了很久才肯定是费用流无疑。

具体怎么看出来的,我把个人分析过程跟你们说一下:首先考虑这个题最可能的算法,看到这种状态不少的题目首先就是思考能不能搜索,而后发现复杂度过高过不去。接着就是动态规划,笔者是没想出来,可是有一个大概的动态规划的构思,就是根据水管的种类其实不多,一共就15种,可不能够搞一个状压DP试试,读者有兴趣的能够本身思考。而后这题不多是数据结构,那排除了一切不可能的状况以后,剩下的再不可能也是可能的了。因而思考网络流。

咱们能够这么考虑,只要一个格子有了一个头,那么它就必须和旁边的一个格子里的头对应起来,不然就不能造成通路。全部格子的任何一个头都知足这个道理。那么咱们就须要让他们所有匹配。

嗯,匹配,彷佛和网络流沾上点边了。

3.2问题简化1

能够先考虑简单的版本,若是就是给了你这些水管的状态,也没让你转动他们,你是否可以用网络流算出来它能不能造成一条通路呢?答案是能够,咱们发现一个地方的水管只和它上下左右的水管有着直接的联系。这样咱们就把这个简化过的问题精简成了一个经典的网络流题目的模型,没错,黑白染色模型。咱们拆点以后求最大流是不是水管的接头数就能够了。

3.3问题简化2

咱们把这个简单的版本升华一下,如今你能够转动这些水管了,咱们能不能使它造成一个通路呢?答案仍然是可行的,咱们只须要拆完点以后把能够转换到的状态之间互相连边就能够了。仍然是求最大流是不是水管接头数。

3.4回到题目中

如今咱们回到原来的题目中,不难发现,咱们只要在问题简化2的基础上增长一个费用就能够了。那么咱们如何设置这些费用呢?

很明显这个题有三种不一样样式的格子,一个是死胡同形,一个是L形,还有一个T形。剩下的两种要么不能动,要么动了和没动同样,不须要讨论。
对于死胡同形,很明显转到旁边须要1的花费,而转到对面须要2的花费。
对于L形,不大明显的是咱们转动90度就至关于把一条边转到了对面,而180度就是彻底旋转过去。。。
对于T形,能够发现他就是一个反过来的死胡同形,反过来连边就能够了。。

那么这个题就能够套用费用流的板子了:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define inf 1000000001
#define re register
#define ll long long
#define min(a,b) a<b?a:b
#define MAXN 20001
#define MAXM 40000005
#define id1 nm[i][j]
#define id2 (nm[i][j]+(n*m))
#define id3 (nm[i][j]+(n*m*2))
#define id4 (nm[i][j]+(n*m*3))
#define id5 (nm[i][j]+(n*m*4))
using namespace std;
int n,m,s,t;
int head[MAXN],num=-1,tot,dis[MAXN],b[MAXN],maxx;
int to[100005],nxt[100005],w[100005],edis[100005];
int a[2001][2001],nm[2001][2001];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int too,int ww,ll dis)
{
    nxt[++num]=head[from];
    to[num]=too;
    w[num]=ww;
    edis[num]=dis;
    head[from]=num;
}
inline void add(int from,int too,int ww,ll dis)
{
    add_edge(from,too,ww,dis);
    add_edge(too,from,0,-dis);
}
inline bool spfa()
{
    for(re int i=s;i<=t;i++) dis[i]=inf+1;
    memset(b,0,sizeof(b));
    deque<int> q;
    q.push_back(t);
    dis[t]=0;
    b[t]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop_front();
        b[u]=0;
        for(re int i=head[u];i!=-1;i=nxt[i]){
            int v=to[i];
            if(w[i^1]>0&&dis[v]>dis[u]-edis[i]){
                dis[v]=dis[u]-edis[i];
                if(!b[v]){
                    b[v]=1;
                    if(!q.empty()&&dis[v]<dis[q.front()])
                    q.push_front(v);
                    else
                    q.push_back(v);
                }
            }
        }
    }
    return dis[s]<inf;
}
inline int dfs(int u,int low)
{
    b[u]=1;
    if(u==t) return low;
    int diss=0;
    for(re int i=head[u];i!=-1;i=nxt[i]){
        int v=to[i];
        if(w[i]&&!b[v]&&dis[v]==dis[u]-edis[i]){
            int check=dfs(v,min(w[i],low));
            if(check){
                tot+=check*edis[i];
                low-=check;
                diss+=check;
                w[i]-=check;
                w[i^1]+=check;
                if(low==0) break;
            }
        }
    }
    return diss;
}
inline int max_flow()
{
    int ans=0;
    while(spfa()){
        b[t]=1;
        while(b[t]){
            memset(b,0,sizeof(b));
            ans+=dfs(s,inf);    
        }
    }
    return ans;
}
int main() 
{
    //freopen("date.in","r",stdin);
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            a[i][j]=read(),nm[i][j]=(i-1)*m+j;
    s=0;t=5*n*m+1;
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++){
            if((i+j)%2==0) add(s,nm[i][j],inf,0);
            else add(nm[i][j],t,inf,0);
        }
    for(re int i=1;i<=n;i++){
        for(re int j=1;j<=m;j++)
        if((i+j)%2==0){
            if(a[i][j]==1){add(id1,id2,1,0);add(id2,id3,1,1);add(id2,id5,1,1);add(id2,id4,1,2);maxx++;}
            if(a[i][j]==2){add(id1,id3,1,0);add(id3,id2,1,1);add(id3,id4,1,1);add(id3,id5,1,2);maxx++;}
            if(a[i][j]==4){add(id1,id4,1,0);add(id4,id3,1,1);add(id4,id5,1,1);add(id4,id2,1,2);maxx++;}
            if(a[i][j]==8){add(id1,id5,1,0);add(id5,id2,1,1);add(id5,id4,1,1);add(id5,id3,1,2);maxx++;}
            if(a[i][j]==3){add(id1,id2,1,0);add(id1,id3,1,0);add(id2,id4,1,1);add(id3,id5,1,1);maxx+=2;}
            if(a[i][j]==6){add(id1,id3,1,0);add(id1,id4,1,0);add(id3,id5,1,1);add(id4,id2,1,1);maxx+=2;}
            if(a[i][j]==9){add(id1,id5,1,0);add(id1,id2,1,0);add(id5,id3,1,1);add(id2,id4,1,1);maxx+=2;}
            if(a[i][j]==12){add(id1,id4,1,0);add(id1,id5,1,0);add(id4,id2,1,1);add(id5,id3,1,1);maxx+=2;}
            if(a[i][j]==7){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id4,1,0);add(id2,id5,1,1);add(id3,id5,1,2);add(id4,id5,1,1);maxx+=3;}
            if(a[i][j]==11){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id5,1,0);add(id2,id4,1,2);add(id3,id4,1,1);add(id5,id4,1,1);maxx+=3;}
            if(a[i][j]==13){add(id1,id2,1,0);add(id1,id4,1,0);add(id1,id5,1,0);add(id2,id3,1,1);add(id4,id3,1,1);add(id5,id3,1,2);maxx+=3;}
            if(a[i][j]==14){add(id1,id3,1,0);add(id1,id4,1,0);add(id1,id5,1,0);add(id3,id2,1,1);add(id4,id2,1,2);add(id5,id2,1,1);maxx+=3;}         
            if(a[i][j]==5){add(id1,id2,1,0);add(id1,id4,1,0);maxx+=2;}
            if(a[i][j]==10){add(id1,id3,1,0);add(id1,id5,1,0);maxx+=2;}
            if(a[i][j]==15){add(id1,id2,1,0);add(id1,id3,1,0);add(id1,id4,1,0);add(id1,id5,1,0);maxx+=4;}
        } else {
            if(a[i][j]==1){add(id2,id1,1,0);add(id3,id2,1,1);add(id5,id2,1,1);add(id4,id2,1,2);maxx++;}
            if(a[i][j]==2){add(id3,id1,1,0);add(id2,id3,1,1);add(id4,id3,1,1);add(id5,id3,1,2);maxx++;}
            if(a[i][j]==4){add(id4,id1,1,0);add(id3,id4,1,1);add(id5,id4,1,1);add(id2,id4,1,2);maxx++;}
            if(a[i][j]==8){add(id5,id1,1,0);add(id2,id5,1,1);add(id4,id5,1,1);add(id3,id5,1,2);maxx++;}
            if(a[i][j]==3){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id2,1,1);add(id5,id3,1,1);maxx+=2;}
            if(a[i][j]==6){add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id3,1,1);add(id2,id4,1,1);maxx+=2;}
            if(a[i][j]==9){add(id5,id1,1,0);add(id2,id1,1,0);add(id3,id5,1,1);add(id4,id2,1,1);maxx+=2;}
            if(a[i][j]==12){add(id4,id1,1,0);add(id5,id1,1,0);add(id2,id4,1,1);add(id3,id5,1,1);maxx+=2;}
            if(a[i][j]==7){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id2,1,1);add(id5,id3,1,2);add(id5,id4,1,1);maxx+=3;}
            if(a[i][j]==11){add(id2,id1,1,0);add(id3,id1,1,0);add(id5,id1,1,0);add(id4,id2,1,2);add(id4,id3,1,1);add(id4,id5,1,1);maxx+=3;}
            if(a[i][j]==13){add(id2,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);add(id3,id2,1,1);add(id3,id4,1,1);add(id3,id5,1,2);maxx+=3;}
            if(a[i][j]==14){add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);add(id2,id3,1,1);add(id2,id4,1,2);add(id2,id5,1,1);maxx+=3;} 
            if(a[i][j]==5){add(id2,id1,1,0);add(id4,id1,1,0);maxx+=2;}
            if(a[i][j]==10){add(id3,id1,1,0);add(id5,id1,1,0);maxx+=2;}
            if(a[i][j]==15){add(id2,id1,1,0);add(id3,id1,1,0);add(id4,id1,1,0);add(id5,id1,1,0);maxx+=4;}
        }
    }
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++){
            if((i+j)%2==0){
                if(i>1) add(id2,id4-m,1,0);
                if(j>1) add(id5,id3-1,1,0);
                if(i<n) add(id4,id2+m,1,0);
                if(j<m) add(id3,id5+1,1,0);
            }
        }
    int d=max_flow();
    cout<<((maxx==d<<1)?tot:-1);
    return 0;
}

4.[HAOI2017]新型城市化

题目描述

Anihc国有n座城市.城市之间存在若一些贸易合做关系.若是城市x与城市y之间存在贸易协定.那么城市文和城市y则是一对贸易伙伴(注意:(x,y)和(y,x))是同一对城市)。

为了实现新型城市化.实现统筹城乡一体化以及发挥城市群辐射与带动做用.国 决定规划新型城市关系。一些城市可以被称为城市群的条件是:这些城市两两都是贸易伙伴。 因为Anihc国以前也一直很重视城市关系建设.因此能够保证在目前已存在的贸易合做关系的状况下Anihc的n座城市能够刚好被划分为不超过两个城市群。

为了建设新型城市关系Anihc国想要选出两个以前并非贸易伙伴的城市.使这两个城市成为贸易伙伴.而且要求在这两个城市成为贸易伙伴以后.最大城市群的大小至少比他们成为贸易伙伴以前的最大城市群的大小增长1。

Anihc国须要在下一次会议上讨论扩大建设新型城市关系的问题.因此要请你求出在哪些城市之间创建贸易伙伴关系能够使得这个条件成立.即创建此关系先后的最大城市群的 大小至少相差1。

输入输出格式

输入格式:

第一行2个整数n,m.表示城市的个数,目前尚未创建贸易伙伴关系的城市的对数。

接下来m行,每行2个整数x,y表示城市x,y之间目前尚未创建贸易伙伴关系。

输出格式:

第一行yi个整数ans,表示符合条件的城市的对数.注意(x,y)与(y,x)算同一对城市。

接下来Ans行,每行两个整数,表示一对能够选择来创建贸易伙伴关系的城市。对于 一对城市x,y请先输出编号更小的那一个。最后城市对与城市对之间顺序请按照字典序从小到大输出。

输入输出样例

输入样例#1:

5 3
1 5
2 4
2 5

输出样例#1:

2
1 5
2 4

说明

数据规模与约定

数据点1: n≤16

数据点2: n≤16

数据点3~5: n≤100

数据点6: n≤500

数据点7~10: n≤10000

对于全部的数据保证:n <= 10000,0 <= m <= min (150000,n(n-1)/2).保证输入的城市关系中不会出现(x,x)这样的关系.同一对城市也不会出现两次(无重边.无自环)。

Solution

既然把题放在这里了,那么天然就不会像题解里写的solution同样了。

其实这个题的主体思路在前面的几个分块中没有来的及体现出来,涉及到二分图的问题。这也是笔者一个失误,二分图类的问题必定近期补到前面去。

那么咱们继续来看这道题。题目给出的是一些城市之间的关系。而后看那个城市群的定义,这些城市两两之间可以互相到达。加上给出的城市之间的关系是之间没有路径可以互相到达,那么就比较明显了,若是咱们根据题目中要求把城市连起来,那么咱们必定能够获得一个二分图。(为何忽略了只能是一个城市群的状况??由于就一个城市群你也没有能够创建的关系了,这么出题没有意义。)

而后咱们仔细考虑这个题让咱们干什么,在两个原本没有关系的城市之间创建关系,就是从咱们刚刚创建的二分图里面删去一条边,最大城市群的大小增长1,就是让咱们这个二分图里的最大独立集增长1。又由于定理:最大独立集=总点数-最小覆盖集=最大匹配。因此咱们要求的就是删去一条边能够使二分图的最大匹配减少1的总边数。而后咱们能够用网络流来实现。

那么咱们如今就有了一个初步的作法了,大致思路就是枚举全部的边,而后不停的网络流,以后慢慢的计算有那些边能够删掉。不过这样过5个点也就差很少快超时了,网络上有大佬说退流或许可以达到更好的时间效率,不过我看上去并无从根本上优化算法,因此过掉第6个点仍是悬。那么咱们有没有更优化的方法呢?

有的,根据定理:若一条边必定在最大匹配中,则在最终的残量网络中,这条边必定满流,且这条边的两个顶点必定不在同一个强连通份量中。 可得,咱们只须要经过残量网络跑一遍Tarjan就能够了。那么这个定理又是怎么来的呢。。(话说为何有这么多的定理。)

笔者浅显的证实一下:首先要满流,而后不在同一个强连通份量里是指,若是在同一个强连通份量里,那么咱们在这个强连通份量内部增广一下,整个份量里的残量都会变化,可是网络的最大匹配并不会变化,也就是说咱们又能获得一个新的最大匹配,也就是说这条边的存在是无可厚非的。

那么能够放上代码了。

Code

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <set>
#include <map>
#define MAXN 200000
#define re register
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(arr) memset(arr, 0, sizeof(arr))
const int inf = 0x3f3f3f3f;
struct po
{
    int nxt,to,w,from;    
}edge[250001];
struct ANS
{
    int x,y;
}ans[MAXN];
int n,m,cur[MAXN],head[20002],num=-1,dep[20002],s,t,c[MAXN],vis[20002];
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
    while(ch=='-') c*=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
inline void add_edge(int from,int to,int w)
{
    edge[++num].nxt=head[from];
    edge[num].from=from;
    edge[num].to=to;
    edge[num].w=w;
    head[from]=num;
}
inline void add(int from,int to,int w)
{
    add_edge(from,to,w);
    add_edge(to,from,0);
}
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    queue<int> q;
    while(!q.empty())
    q.pop();
    q.push(s);
    dep[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(re int i=head[u];i!=-1;i=edge[i].nxt)
        {
            int v=edge[i].to;
            if(dep[v]==0&&edge[i].w>0)
            {
                dep[v]=dep[u]+1;
                if(v==t)
                return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
inline int dfs(int u,int dis)
{
    if(u==t)
    return dis;
    int diss=0;
    for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].to;
        if(edge[i].w!=0&&dep[v]==dep[u]+1)
        {
            int check=dfs(v,min(dis,edge[i].w));
            if(check!=0)
            {
                dis-=check;
                diss+=check;
                edge[i].w-=check;
                edge[i^1].w+=check;
                if(dis==0) break;
            }
        }
    }
    return diss;
}
inline void dinic()
{
    while(bfs())
    {
        for(re int i=s;i<=t;i++)
        cur[i]=head[i];
        while(int d=dfs(s,inf));
    }
}
void put_color(int u,int col)
{
    c[u]=col;
    vis[u]=1;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        int v=edge[i].to;
        if(!vis[v]) put_color(v,col^1);
    }
}
int dfn[MAXN],low[MAXN],stack[MAXN],color_num,color[MAXN],cnt,top;
inline void Tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    vis[u]=1;
    stack[++top]=u;
    for(re int i=head[u];i!=-1;i=edge[i].nxt){
        if(!edge[i].w){
            int v=edge[i].to;
            if(!dfn[v]){
                Tarjan(v);
                low[u]=min(low[u],low[v]);
            } else if(vis[v]) low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u]){
        color[u]=++color_num;
        vis[u]=0;
        while(stack[top]!=u){
            color[stack[top]]=color_num;
            vis[stack[top--]]=0;
        }
        top--;
    }
}
int x[MAXN],y[MAXN],tot;
inline bool cmp(ANS a,ANS b){
    return a.x==b.x?a.y<b.y:a.x<b.x;
}
int main() 
{
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(re int i=1;i<=m;i++){
        x[i]=read();y[i]=read();
        add_edge(x[i],y[i],0);
        add_edge(y[i],x[i],1);
    }
    
    for(re int i=1;i<=n;i++)
        if(!vis[i]) put_color(i,2);
    memset(head,-1,sizeof(head));
    s=0,t=n+1;num=-1;
    for(re int i=1;i<=n;i++){
        if(c[i]==2)
            add(s,i,1);
        else add(i,t,1);
    }
    for(re int i=1;i<=m;i++){
        if(c[x[i]]==2)
            add(x[i],y[i],1);
        else add(y[i],x[i],1);
    }
    dinic();
    memset(vis,0,sizeof(0));
    for(re int i=1;i<=n;i++)
        if(!dfn[i]) Tarjan(i);
    for(re int i=0;i<=num;i+=2){
        int u=edge[i].from,v=edge[i].to;
        if(!edge[i].w&&color[u]!=color[v]&&u!=s&&v!=t&&u!=t&&v!=s){
            if(u>v) swap(u,v);
            ans[++tot].x=u;ans[tot].y=v;
        }
    }
    
    sort(ans+1,ans+tot+1,cmp);
    cout<<tot<<endl;
    for(re int i=1;i<=tot;i++)
        printf("%d %d\n", ans[i].x,ans[i].y);
    return 0;
}
/*6 5 3 7 2 4 1*/

待续。。。

相关文章
相关标签/搜索