1.前言
整个比赛历时70多天,是一场持久战,期间看了不少资料,实现了一些算法,现总结如下。
初赛的思路是启发式+费用流,启发式实现了遗传算法、模拟退火,费用流实现了连续最短路(spfa),zkw/primal-dual,网络单纯形(决赛写的),这些算法其实都不是自己手写的,是从网上的模板改造过来的。
初赛规模用退火还可以,到了复赛,服务器分等级,规模变得更大了,费用流的次数完全不能满足退火的需求,于是放弃找全局最优,直接贪心找局部最优。
决赛题目大变化,可怜我花了一周的时间整了个网络单纯形,后来才发现根本没有用上。决赛主要是想策略,对费用流的速度要求不是那么高。
2.小费用最大流问题求解算法
先说一下小费用最大流问题求解算法有哪些,从《网络优化》(谢金星 et al., 2009)一书中可以看到,求解该问题有如下这些方法:
- 消圈算法
- 最小费用路算法(连续最短路)
- 原始-对偶算法
- 瑕疵算法
- 松弛算法
- 网络单纯形算法
还有一个zkw算法不在这本书中,其实zkw用于比赛,效率非常高。
2.1最小费用路算法(连续最短路)
这算法的实现来自于《算法竞赛入门经典(第2版)(算法艺术与信息学竞赛)》(刘汝佳,2014)一书,当时正好在看这本书,就直接用这里的算法了,包括图的存储结构也用的这本书里面的,应该叫做前向星。
2.2 zkw/primal-dual
问题规模变大后,前面的算法速度已经跟不上了,就得去找新的算法,一开始就听说松弛和网络单纯形很快,但是觉得它们实现起来很麻烦,想找一个简单一点的,就发现了zkw(从入门到精通: 最小费用流的“zkw算法”),连续最短路是一条路一条路的增广,而zkw是多路增广,快了不少。
2.3网络单纯形算法
进决赛后,题目还没有定下来的时候,我们觉得应该看看网络单纯形,要是规模继续变大,zkw可能也跟不上,搜了搜网络单纯形的开源库,找到3个,分别测试了一下,时间上,如果zkw要30ms,网络单纯形可以10ms,还是有一定的价值实现。网络单纯形的大概原理可以看《网络优化》(谢金星 et al., 2009)。
不过要注意网络单纯形并不等同于线性规则里面的单纯形,可以看看《运筹学(第三版),清华大学出版社》。
我搜到的网络单纯形的3个开源库分别是:
- MCFClass project (C++)
- LEMON (C++)
- NetworkX (python)
NetworkX可能是因为python的原因,速度非常慢,看它的源码和LEMON很像。
2.3.1 MCFClass project
进去需要翻墙,下载下来后在说明文档中有“How to Use It”,可以参照进行使用,注意需要将图存为它规定的格式,这里提供2个测试例子。一个是800点的图,服务器位置使用最优解的位置。一个是第三批练习用例里面的case0,这个例子有1200个点,5267条边,480个消费点,服务器位置是跑退火得到的位置。设置一个超级源点,连接所有服务器,设置一个超级源点,连接所有消费点。两个case可以在这里下载,数据格式在文档中可以查看。
所以,如果想要快速上手使用它,可按照如下步骤进行(以win, vs2013为例):
1.下载源码,下载我提供的case
2.新建一个vs工程,创建h文件和cpp文件,把下载下来的文件内容复制过去,或者直接替换(用c文件好像会出问题,不行就用cpp)
3.编译(release)
4.把下载的case放在release文件夹中,在当前文件夹中打开命令行,运行:
***.exe case**.txt
就可以看到结果了,结果会显示时间和最小费用。
2.3.2 LEMON
LEMON里面的最小费用流算法由(Király and Kovács, 2012)完成,可以参考他们的文章:Király, Z. and Kovács, P., 2012. Efficient implementations of minimum-cost flow algorithms. Computer Science.文章对他们的网络单纯形算法进行了介绍。
在windows下的安装方法,不出意外的话,按照里面提供的方法可以产生一个vs工程。LEMON同样需要使用它指定的数据格式,可以参照文档说明进行生成,这里提供一个我生成的case是1200点的图,服务器位置使用退火获得的(随意跑的?还是哪来的?忘了),设置一个超级源点,连接所有服务器,设置一个超级源点,连接所有消费点,case可以在这里下载。
所以,如果想要快速上手使用它,可按照如下步骤进行(以win, vs2013为例):
1.下载源码,在windows下安装(方法)
2.下载我提供的case(可以在这里下载),下载后放在...lemon-1.3.1uilddemo
下。
3.为了测试方便,我直接把lgf_demo.cc修改了,修改后的源码在这里,下载下来放到lgf_demo.cc中。
3.把lgf_demo设为启动项,在release模式下运行,就可以看到结果了。
注意:
- LEMON实现了好几种搜索入弧的方式,默认的是
BLOCK_SEARCH
,说这个的效率高且稳定、实现起来比较简单,ALTERING_LIST
可能效率高但不稳定,实现要复杂一些。 - 存储边的时候采用了乱序存储,这样可以提高效率,我没有搞明白为什么,如果我不采样乱序存储速度确实会变慢。这里说的乱序就直接按照数据文件读取的时候边的顺序是1,2,3,4,5,6,7,8,9,10.那么存的时候就按一定的间隔存,比如间隔为2,那么存起来就是这样的:1,3,5,7,9,2,4,6,8,10.
- 网络单纯形居然也有参数要调整,比如
BLOCK_SEARCH
的块大小,如果为1就退化成了FIRST_ELIGIBLE
,如果太大或者直接是边数,那么就成了BEST_ELIGIBLE
.
2.3.3 NetworkX
我感觉NetworkX里面的网络单纯形像是从LEMON里面提取出来后简化过的,测试发现花的时间很长,可能是因为python的缘故。
同样,如果想要快速上手使用它,可按照如下步骤进行(以win, python3为例,我用的anaconda):
1.按照文档说明进行安装
2.它也需要一定的数据格式,这里提供一个可以用的case(下载),是第三批高级case0,1200个点。
3.下载测试脚本
4.运行就可以看到结果
补充:
netcan指出还有ξ松弛,拍卖算法里的,不用退流,但无解的情况特别慢,提供流量和需求流量不一样得到的解也不对。