1.问题描述

一个石油传送网络可由一个加权有向无环图G表示。该图中有一个称为源点的顶点S ( 保证S的入度为0 ) , 从S出发 , 石油流向图中的其他顶点 。G中每条边上的权重为它所连接的两点间的距离。

在输送石油的过程中 , 需要有一定的压力才能使石油从一个点到达另一个点,但压力会随着路程的增加而降低 ( 即压力的损失量是路程的函数 ) 。

因此为了保证石油在网络的正常运输,在网络传输中必须保证在任何位置的压力都要不小于最小压力Pmin。为了维持这个最小压力,可在G中的一些或全部顶点放置压力放大器 , 压力放大器可以将压力恢复至该网络允许的最大压力Pmax。

可以设d为石油从压力Pmax降为压力Pmin所走的距离 , 在无压力放大器的情况下石油可运输的距离不超过d .

在该问题中 , 需要在G中放置最少的压力放大器 , 使得该传输网络从S出发,能够将汽油输送到图中的所有位置

2.相关假设

①为了简化计算 , 每走一个单位长度就会降低一个单位压力且Pmin=0 , 即d=Pmax - Pmin = Pmax
② 起点S处的压力为Pmax
③如果存在一个点x , x有多条入边e[1]…e[n] , x处的压力p[x] = max(p[x], p[e[i].from] - e[i].cost)即x处的压力为到达x处的压力中的最大值
图中每条边上的任何一点都要能到达: 例如 如果存在一个点x , x处的压力>Pmin , 但存在一条边 e , p[e.from] - e.cost < Pmin 这也是不合法的。

3.基本要求

①给出两种方法以解决上述问题,思考如何验证两种方法的正确性.
②比较两种方法的时间和空间性能,用图表显示比较结果。

4.数据集描述

①该数据集提供100组石油网络的数据 , 可自行选择进行测试 , 输入数据在input文件夹下, 标准输出数据在outputSTD文件夹下
②在Image文件夹下存放了每组结果的可视化图片 , 标注了边权 , 压力 , 是否放置放大器 . (注 : 仅供参考 , 可能存在多种方案)
③该数据集保证加权有向无环图 , 保证只有一个入度为0的源点
④根目录下的make.py为数据生成器 , 数据生成器依赖于库cyaron , 你可以阅读该文档以了解其使用方法和原理 cyaron并没有指出生成的dag只有一个源点 , 但他的写法确实只有一个源点

5. 数据集格式

输入数据 :

第一行包含三个整数 [n, m, l] , 源点均为 1 号节点

  • n 表示节点个数
  • m 表示边的个数
  • l 表示最大边权 ( 可以认为是Pmax )

接下来 m 行 , 每行三个正整数[x, y, c]用来标识一条有向边

  • x 表示起点
  • y 表示终点
  • c 表示边权

输出数据 :

一行一个整数 , 表示最少的放大器的个数


题解

在这里插入图片描述
以图形化输出solve12.jpg为例

输入建立了从结点i到j的多条路径,其中有用的是最短路径最长路径。用Pi 表示nodei 的压力,对于边nodei -> nodej,nodej 的压力等于nodei 的压力减去路程耗费cost。初始时只有node1的压力是确定的,其它所有结点都根据石油运送过程中的路径损失、压力放大器的设置点来更新。

根据题意要求,对此题分别按最短边和最长边建图,nodei -> nodej 的最短边用来更新nodej 的压力,最长边用来作边界条件,检测该放大器设置是否合理。即,在保证最长边(最大cost)能到达的前提下,用最小边(最小cost)来更新nodej 的压力。

例如solve12.jpg,如果 node7 不放增压器,pre7 = 19,虽然pre8 的值是用 node6 -> node8 的耗费来更新的,但此时 node7 无法到达 node8 ,因此 node7 必须要放增压器。

方法

本题方法众多,已了解到的同学们使用的方法就包括枚举、剪枝、prim、Floyd、分支定界等,在此介绍利用bfs贪心利用dfs分支定界两种方法。

①bfs贪心:
首先,对于所给的图,记录它的拓扑序列,把源点 node1 插入到队列中,按照拓扑序列对图进行bfs遍历。
例如solve12.jpg:
在这里插入图片描述
按照插入边的顺序,它的拓扑序列为:[1, 2, 3, 4, 5, 6, 7, 8]。用p来表示当前结点的压力大小。此时检查1,由于1是源点,故p = Pmax = 51。然后,检查所有与1相邻接的点,即2、3、6,由于1到2、3、6的最大花费的边都能够成功到达,因此1不必放加压器。于是,用最小花费的边来更新2、3、6结点的压力值。此时pre2 = 1,pre3 = 11,pre6 = 8。

然后考察2,此时p = 1。与2相邻接的点有4、5、6,按照结点序列先检查4,从2可以到达4,更新pre4 = 0;但发现从2不可以到达5,于是给结点2放上加压器,重新按照序列检查、更新2能到达的所有结点,也就是再从4开始更新一遍,置p = Pmax,pre2 = Pmax,pre4 = 50,pre5 = 10,pre6 = 29。

接着,按照上述步骤,依次检查图中的所有结点。总体思想是,按照拓扑序列检查到nodei 时,按顺序检查nodei 能到达的所有结点,如果发现从nodei -> nodej 不能到达,那么给nodei 放上加压器,重新按顺序检查、更新一遍 nodei 能到达的所有结点。检查时用最大边,更新时用最小边。

由于每次只是贪心地考虑当前结点,因此结果集可能会比标准结果集大一点,但经过测试,绝大多数情况下贪心的方法即是标准结果。

②dfs分支定界:
本题使用dfs进行递归也比较直观。使用一个结果集Vset[]表示当前图中加了放大器的点,初始时都是0。从源点1开始到结点n进行dfs,每搜索到一个新的结点,就对这个点进行左递归和右递归,左递归表示这个点不加压力放大器,右递归表示这个点要加压力放大器。递归完毕后保证每个点都被验证过0和1两种状态,有点类似穷举的方法,在全部可行解中找出最小的一个可行解。

在每次递归后,判断在当前加压情况下,从s出发是否能到达每一个点,如果能到达,说明这是一个可以考虑的分支,反之则说明这不是一个可考虑的分支。

如果这是一个可考虑的分支,那么要做对应的修改:如果得到的结果集比之前得到的小,即有更小的加压方式,那么把加压方式的数组和加压点个数进行更新。在所有修改操作后,筛选出结果集最小的一种情况。

反之,如果不是一个可考虑的分支,那么不做任何修改。判断该分支是否可行的方法也是利用了拓扑序列进行bfs遍历,可参考代码中相应部分。由于拓扑序列记录了每个点的入度,因此也可以顺便记录其出度,对于此方法,可以进行剪枝优化:把出度为0的点全部剪掉。

更新一个同学的做法:

③全排列:
每一个结点根据放置放大器和不放放大器可有1和0两种状态,求出所有结点状态的全排列,即列出所有可能的放置情况。然后,对于每一种放置情况,都对图进行一次遍历,看这种放置方法是否可行,对于所有的可行解维护一个最小值。

这种方法也是暴力枚举的思想,复杂度比较高,但确实写起来比较简单,遍历图也只需要一个函数就能完成。


文件下载地址

https://github.com/zj23333/CDDSA/

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐