先贴上我们名次,我们是上合赛区的【上江湖南西海】队。初赛38名,复活赛8,然后就game over啦:
这次的赛题依旧涉及图论相关的算法。去年是【未来网络:寻路】,今年是【大视频时代:布局】。连续两年都是图论了,建议明年准备参加比赛的同学可以先把图论给温习了。
细分的话,这是一个CDN问题,或者叫做server placement/ facility location问题,总之搜这几个关键词可以拿到很多有价值的论文。建议明年准备参加比赛的同学,拿到赛题后先把问题模型搞清楚,确定其在学术界的名称,然后就可以去搜论文了。看论文是一个非常重要的集思广益的过程。
简要地给出我们的timeline:
1.一开始打算借鉴去年的线性规划。
比赛前期遇到期末考试,断断续续用了一周在Lingo把官网case跑出来了,得到最优解783,但是耗时4s,时间太长了。又尝试自己写单纯形法来解整数规划,逐渐认定手写至少很难和商业上成熟的软件相比,耗时问题难以解决,遂放弃。后来网上有人分享,他用整数规划成功解决大case,用时超过2小时,而解决QQ群里面非官方的终极case则需要耗时一天以上,这条路的确行不通。
2.大事化小,先解决CDN位置确定时的最小费用最大流问题。
谢师兄成功写出了SPFA算法,这个是我们比赛过程中的一个关键点。接着我加入超级源点和超级汇点的思想:超级源点连接设定好的CDN(为了方便,接下来服务器都会被简称为CDN),超级汇点连接所有的消费节点所连接的网络节点,并且保证所有消费节点连接到超级汇点的链路带宽即为其带宽需求。至此,当确定某几个节点为CDN时,可以求出最大流为且只能为固定值时的最小费用问题(后期注:另外据可靠消息,zkw算法比SPFA更适用于此图)。所以接下来我们只需要想方法来确定CDN位置。
3.用遗传来确定CDN位置。
当时摆在我们面前的主要有退火、遗传这两种启发式搜索方案,我们选择了遗传算法。现在回想起来,这个选择注定是一条更难的路:首先在选择初始种群时走了弯路,尝试了很多种方法来筛选出"CDN候选点",也就是筛选出哪些点可以作为基因,想来减少后期的计算量;其次种群的迭代太慢了,迭代出来的也不一定是可行解;并且遗传有个关键:要保证产生的子代很大几率上是优于父代,才能保证整个过程是在优胜劣汰。这个关键点我们没有处理好,进一步减慢了迭代速度。
4.改用退火算法
后来写出了一版退火算法,立马就上64强了,在50-60名之间。退火更加适合于这道题,我认为关键原因在于:退火的求领域解的机制加大了产生可行解的几率,使得迭代的有效率大幅提升。遗传不是不好,而是比起退火来难很多,导致我们一直没有调试出满意结果,当然也可能是我们写残了。一打听周围的参赛同学,才发现那些早就顺利上榜的人用的都是退火算法。
回过头来,我反思为什么我们在遗传上浪费了太多时间: 不舍得丢弃已有成果,觉得不断努力肯定会有好结果。实际上,应当具备一定的预见能力,觉得行不通尽早换方向。锲而不舍,及时止损,这两者的关系要好好把握与权衡,这可能就是大佬经验比我丰富的地方吧。当然止损也有一条捷径,那就是要在比赛中保持信息畅通,不能闭门造车。如果我们早知道大多数人用的都是退火,自然而然就会更早的转换方向,毕竟重新写一版代码代价太大,没有大把握的话很难有动力去做。所以建议比赛的同学们应当多结识赛友,没人愿意白给你指导,你也得拿有份量的信息去交换,这样得来的信息比在QQ群和博客得来的有用得多。
5.对图预处理、优化与重构。
既然已经上榜,那么艰难的爬榜之路就开始了。代码重构主要是师兄在做,每次优化一点儿,名次都能往上爬几名,但是几小时后又会有人爬到我们前面来。大家都在优化,不进则退放这里是再合适不过了。我在做预处理的程序,筛选出必定为CDN的点。这个规律很好找,大case能筛选出30-50个点,大大减少了运算量,名词进入40-50区间。
6.发现重大规律:只需要在消费点直连的网络节点中筛选即可。
我们之前一直是在所有点中进行CDN选点。直到有一天,看到了有一个同学分享的用线性规划得到的最优解运算结果。我对他的数据统计分析了一下,发现最优解的CDN位置基本均为消费点直连的网络节点,偶尔有一两个落单的点为普通的网络节点。为了效率,我觉得暂时可以直接在消费点直连的网络节点中进行CDN选点即可。因为这个规律的发现,再加上对图预处理的过程,名次进入了30-40区间。所以无论通过什么方法求解,也许是暴力大法,也许是线性规划,虽然这些方法不可以用在最终代码中,但是可以获得用于参考的最优解。这就相当于做习题时拿到了没有运算过程的最终答案,从答案往过程推导也是一个获得思路的好方法。
7.用C++重写。。。
目前为止TOP64至少是保住了,绿卡问题不大了。接下来都在优化与重构,发现Java效率实在底下,迭代次数比C++慢了五倍不止。官方说法是Java和C++差异可以忽略,这个只能参考,千万别采纳。C++作为竞赛语言是有原因的,道理我们都懂,依旧走了弯路,万事都是如此,你不体会一遍就不会长记性。
复活赛竞争异常激烈,复活赛最终的前四放初赛是可以进前15的(此数据仅供参考,并不严谨)。所以能在初赛完成的目标,千万不要拖到复活赛。用CPP重写后,初级和中级接近满分了,高级case实在没时间调试了,若放初赛这结果进前32是稳的。不过没什么可遗憾的,大佬们的确很厉害,希望自己多汲取教训,多掌握经验。
最终我们的代码构成为:退火算法寻址 + SPFA算法求路径 by C++语言。附上github源代码以及目录:
https://github.com/YANYUQI/2017HuaweiCodeCraft数十个赛区,只有西北川渝等赛区的竞争才算是白热化,尤其是成电、西电等高校长期30多个队伍霸屏TOP64榜,而其他赛区进TOP64和拿绿卡难度并不大。所以我明年肯定会推荐学弟学妹们来参加这个比赛,性价比还是很高的。
比赛过程中,偶遇开发者论坛愚人节活动。运气比较好,我们队三个人拿了两套华为code craft卫衣和小原公仔。感谢我的队友:谢师兄和任同学,尤其感谢他们给我做队长的机会,比赛完带队友们去赤坂亭吃了一顿饭,放张照弱弱地纪念一下:
写下这篇文章,主要是给自己队伍的工作做个总结,也给其他和我一样的"小白"提供参赛经验。欢迎关注,欢迎讨论。
网友评论