2019华为软件精英挑战赛比赛经验分享(初赛,复赛,决赛)

比赛成果:

初赛(700+):西北赛区第3。

复赛(32):西北赛区第3。(华为手机v20,华为面试绿卡,西北赛区二等奖,小礼物若干)

决赛(32):全国16强,具体排名13。(没有奖金,纪念品若干,旅游三天)

主要的发车策略:

一秒N辆车上路

软赛经验:

初赛阶段:

初赛赛题公布在3月9号,我们在3月9号凌晨就分析了一波赛题,由于之前刷Leetcode的经验,很容易看出本次的主要算法是迪杰斯特拉单源最短路径算法,用这个算法可以容易地得到某辆车从起点到终点的最短路径,道路权重一开始设置为时间,于是利用迪杰斯特拉算法可以轻易得到单量小车的最短路径。

路网的建立,如果之前刷多了Leetcode的图相关算法的话,会很容易发现官方给的道路,结点相关的txt文件中的数据是非常友好的,直接存储在vector即可轻松构建地图,所以没有做到这一步的小伙伴们可以认为是Leetcode刷少了。

题目中有个非常重要的信息,循环等待(死锁)问题一旦出现则判负,因此避免死锁问题是很重要的。另外每辆小车有计划发车时间(planeTime)和实际发车时间(beginTime),且任务书上提到planTime<=beginTime,这就意味着,小车的发车时间可以推迟。这一点非常重要,这就意味着存在最low的能有成绩的算法,一辆一辆跑,每时刻地图上只有一辆车,这样一来就不可能死锁了。。。

当然,光靠一辆一辆跑,是不可能取得好成绩的,不过由于实现简单,可以验证一下提交的流程是否正确,比如SDK的打包,输入输出,DJ算法,图的构建是否有明显的错误,由于一开始不太习惯官方的打包,这个也弄了一个下午,最后官方系统才出成绩,然后我们才考虑下一步的优化。我们发现,所有车取最短时间的路径极易造成拥堵,因此我们的方案选择是让有一些车适当绕路,当然这一点不是人为设置,也是依赖于迪杰斯特拉算法的改动。之前只考虑时间最短的话这个时侯的路权w = t,如果把通过某一条路的代价w = t + a*num,其中num是已经使用这条道路的次数,这就类似与动态规划的问题,随着某一条道路使用次数越多,后面的车通过这条道路的代价越高,而此刻的迪杰斯特拉算法是为了求解最小代价这样的一个最短路径问题,这样一来就能实现自动绕路。

本次比赛主要的目的是让最后到达的小车时间尽可能小,我们实验室是学物理的,我们一开始也没有调度方面的经验,还想着给出N个小车的路径,在数学上求解到达时间t,这就是所谓的解析解吧,一开始认为存在第一能动车辆,所有车的运动都要因为第一能动车运动之后才能运动,然后问题就变成广度优先算法。很显然过了几天还是解不出,直到自己推翻了自己的想法,就是环流,环流中没有第一能动车辆。这个时候说实话有点慌了,因为这就意味着花了一个礼拜,没有任何效果,但是我们没有放弃。

随后仔细查看任务书,发现官方在给定小车路径的时候是有调度的交通规则的,我们一开始并没有认识到交规有什么用,开始看到群里很多人在讨论交规,都不太理解为什么,还是比赛经验太少所导致的。仔细看了任务书之后,发现求解N个小车已知路径的调度时间并不是一个数学问题,而是一个逻辑问题,这个逻辑依赖于官方给的交规,怎么过路口,路内怎么调度,怎么发车,这就是传说中的判题器,同样官方也给出了死锁逻辑,就是某一轮存在等待车辆无法变成终止车辆,至于等待车辆和终止车辆的定义可查看官方给的任务书中的定义。

这个时候,我们大致明确了方向,就是要先撸出判题器,才能保证不死锁。然而这个判题器要完全对上官方的,是真的难!!!之前感觉就是个逻辑题,感觉凭我6-700的leetcode,这种逻辑题轻轻松松,然而就是太年轻了,搞了一个礼拜还没有完全对上,发现了很多情况都违反了交规,在处理完很多BUG之后,某一次运行发现,线下调度时间3628,线上也是3628,当时天真的以为判题器全对!!!高兴了一晚,后来发现还是不对,吧车辆盖的更密集之后,发现误差就变得大了,仔细分析后发现,过路口还有BUG,又细心查看过路口的逻辑,仔细对比任务书,修改之后我们每次都打印出一秒一辆,一秒两量,一秒三辆车,...,一秒30量车的调度时间以及死锁情况,误差都很小,死锁的点还算准确。

这个时候判断题任然不准,但是离初赛开始已经还剩3天了,这个时刻我们的成绩任然不是很乐观,两个图1000左右,这种成绩在当时只能排到赛区15左右,其实已经能进复赛了,当时误以为初赛前4有手机,所以还想着怎么个优化。根据之前的模型,我们发现,w = t+a*nums这个设置并不是非常合理的,因为地图中的道路有的是双向,有的是单向,并且有的是多车道,还有的是单车道,试想,两条道路等长,分别是1车道和4车道,都有5辆车使用过这条路径,那么难道过这两条路的代价一样吗?显然是不合理的,于是我们将w进行修改,w = t + a*nums/size,其中size为车道个数,并且我们考虑了双向道的因素之后,发车的数量就能变多了。这个时刻,我们的调度时间就到了700左右,还算比较理想。这个时候到了初赛前一天,根据队友的观察,以及一个很能爬的大佬的统计的数据,西北赛区700以内的也就4个,于是感觉还算很稳。

初赛当天,地图变大了很多,车的数量也变多了不少,而且路网出现了非常多的空结点,车ID,道路ID,节点ID都不是连续的,由于Leetcode经验,我在这之前就考虑到了这个因素,于是一开始就先建立了映射关系,考虑了路网是空这种情况,而且还有个伪判题器直接判出每秒可发车的数量,于是初赛那天很快就出了成绩,再加上手动分图调参(大佬勿喷),每张图又快了100多,最终成绩是2200多,西北赛区第3,成功进入复赛。不过初赛是没有奖品的,这个有点小小的不爽,还以为手机有了。

复赛阶段:

第二天复赛的题就出来了,但是由于考虑到初赛前几天撸判题器有点猛,跟队友讨论了好几个晚上到3-4点,讨论交规逻辑,所以就先放了2天假,打了2天LOL放松了下,之后才开始搞复赛的东西。我们吸取了之前的教训,首先仔细看了看任务书,发现比初赛多了不少东西,比如车的种类多了优先车,预置车等等,还有交规又更加复杂了,这对于判题的难度增加了不少。研究发现,之前的发车策略还是能用,只不过是要将优先车先发车,往预置车里面疯狂插入其他车,直到逼近死锁的边界,于是之前的发车策略一直没有大改。
对比之前的信息,道路,节点等信息是可以保持不变的,无非就是车的增加了两个属性,是否预置,是否优先,因此在原有类的基础上增加两个成员即可,略微修改下读取车辆信息的函数即可,此外还需读取预置车的实际发车时间,以及预置车的路线,输入部分只需做以上修改即可。规划路径还是跟之前一样,在设置每秒发车数量N之后,除了预置车部分可以超过这个发车数量之外,其余皆不可。因此,我们先记录预置车的发车时间,假如某个时刻预置车超过了N,那么非预置车不允许在当前时刻发车;假如某个时刻预置车数量不足N,那么还可以在这个时刻发非预置车直到数量达到N为止。

另外,系统调度时间是 优先车到达时间*a + 总到达时间,其中a为常数,这也就意味着,应该尽可能地让优先车先到达终点。除此之外,为了确保公平性(车的优先属性一致时才考虑),也就是计划发车时间小的车辆,要确保先发车(题目中没有提到这一条,但是个人认为在实际应用中很重要),即:

(planeTime[i] - planeTime[j])*(beginTime[i] - beginTime[j]) >= 0 ;

于是我们定义了以下的比较函数,来对小车进行排序。我们对小车的优先级排序,优先车比非优先车先安排发车时间;对与优先级一样的车,则计划发车时间小的先安排发车时间,排序之后,再按照顺序对小车进行路径规划,和初赛一致,考虑道路代价的变化,双向道,以及车道数量等等,于是发车策略就很快匹配了之前初赛的特点。

复赛,跟初赛一样,也是可以分图调参,所以我们并没有想过如何自动调参,再加上判题器比较复杂,就没怎么做判题器的部分,当时判题器还是不准的,但是可以大致估算每秒发车数量的上限,这在复赛当天也起到了关键作用,于是我们制定了手动调参的策略。其中步骤主要分为以下几个步骤:

1. 将所有的非预置车量时间往后推迟,直到与预置车不相关。

2. 非预置车中,通过线下的判题器粗略估算非预置车的发车数量N,在此基础上往上调,通过手动调参提交到系统上面,得到不死锁的最大值Nmax。Nmax可以认为是本图每秒适合发车的数目。

3. 设置预置车堆中的发车数目n,也是用线下的判题器先估算一个合适的值,然后往上加,和之前一样手动调参提交到系统上面,得到不死锁的最大值nmax。nmax可以认为是有预置车堆中能够发车的数目。

4. 实际情况是,预置车的发车时间分布并不是均匀的,因此存在微调的可能性,经过研究发现,复赛练习地图而言,通过微调,每张地图的成绩可以提高100多,收益还是比较大的,但是缺点在于微调需要消耗更多的时间,当时微调之后成绩在5000左右,进入赛区前4的希望还是有的,当时感觉能进前10还是不错的。

复赛当天与初赛不一样的是,不仅仅地图换了,而且题目需求也做了一定的更改,要求是可以选择更改10%预置车的路线。另外复赛需要现场修改代码,而且时间只有两个半小时,相比初赛的8小时短了不少。复赛比赛那天,当收到变更需求之后,我们决定先不改需求,以及放弃微调,先得到一个有效成绩。对于两个地图而言,分别进行步骤1-3,分别得出他们的最优参数(两个发车数量),打包提交之后,成绩在3700左右。当时我们对的外交官各种打听,现场有两三个队伍的成绩到了3600以内,而且我们在其他赛区的友军也远远小于这个成绩。这个时候,还有一个多小时,我们准备改代码,经过分析之后发现,预置车中主要导致死锁的是那些路线比较长的,如果能够将路线较长的预置车重新规划,很可能将路线变短,路网的压力就会随之下降,发车的数量就会随之上升;反之,如果修改的预置车路线较短,可能对于路网而言,影响就不会很大,因此我们决定对路线最长的那些预置车重新规划路径。

经过仔细分析,发现,需要实现这个功能,只需要在原有的代码下,稍作修改即可。具体的步骤如下:

1. 由于更改的预置车是不能修改发车时间的,因此我们应该先对所有车量按照原先的方法,先安排发车时间。

2. 获取路线最长的10%预置车ID,且将他们的预置车属性变成非预置车属性。

3. 按照顺序获取每辆车的路线,预置车除外即可。

修改完这个想法之后,成绩瞬间提高了不少,到了3300多,这个时候比赛也快结束了,由于是取最后一次的成绩,所以我们并没有做后续的修改。

比赛当天,西安华为顺便邀请现场的西北32强参加晚宴,晚餐还算丰盛。不得不说最终的颁奖还是很骚的,先公布18-32,然后公布6-17。随后单独宣布第5,要知道前4才能领绿卡,手机以及去深圳参加决赛,我感觉第5还是比较尴尬的,不知道官方是这么想的。由于我队外交官各种打听成绩,所以我们早就知道前4的成绩的了,最终,西北工业大学斩获西北第一,印象中是3000多的成绩遥遥领先。之后的西北2,3,4名都来自西电,不得不说西电的小伙伴还是很给力的。

决赛阶段:

随后的第二天,决赛的题目公布了,加了AI部分,车牌识别的内容,之前初赛前华为有个宣讲,提到了决赛会涉及人工智能部分,深度学习和计算机视觉部分,当时感觉要西北前4才能进决赛,从来没有想过自己能进入决赛,于是压根就没有准备这方面相关的内容。另外,官方明确,决赛只给32进16的两个地图,随后的地图都是不公布的,所以分图调参在决赛时不存在的。

我们仔细看了任务书,车牌识别和车辆调度是相互独立的部分,所以我们做了明确的分工,由于之前调度相关的代码大部分都是我写的,所以决赛部分我负责修改判题器,完成自动调参这个算法,其余两名队友负责研究车牌识别,华为云的相关操作。自动调参算法可以利用二分法调参,效率比较明显,速度也相对较快,对于一个600-700Leetcode小码农而言就是分分钟的事,关键在于判题器的准确性,在修改判题器之前,我们用自动寻参算法找出的解不太理想,所以我们决定要撸对这个判题器。

找判题器的BUG是一个漫长而痛苦的过程,我感觉仅仅依靠一个人是很难发现问题的,我们之前的判题器虽然对不上官方系统,但是已经非常接近了,所以应该是出现了一些小概率事件的错误所导致的,很难发现问题,而事实上阅读了非常多次的代码,任然发现不了问题,甚至将代码讲解给两名队友,也没有发现其中的问题。后来,我们找到了一个其他赛区的友军,他们的判题器也跟官方不一致(存在BUG),我们通过合作,两人打印出log(内容包含每秒所有道路的路况),对比查找第一个不一样的地方,分析原因,发现错误,最终得到了比较理想的判题器。

在分析的过程中发现,可视化工具是非常重要的,在分析出现异常的路口时,由于没有可视化,我们只能手动画图,标记车辆ID属性等等,效率极低,基本每分析一个路口发现问题都是需要2-3小时,这个就花了大概1个多礼拜,而决赛训练阶段一共也就两个礼拜的时间,所以深刻认识到只会C++是不够的,还要多学学python,cv等等,这对查找逻辑比较复杂的程序BUG很有帮助。

随着时间的推迟,队友的车牌识别也弄得差不多了,离去深圳也越来越接近,决赛训练工作也接近尾声,我们当时去深圳之前的在榜排名在10左右,当然要计算榜下的藏分大佬,可能连16强都进不去,觉得离奖金(全国前8)遥遥无期,但是由于决赛那天还要改需求,所以我们仍然信心满满,不放弃最后一丝希望,觉得前8还是有一丝希望的。

决赛当天,需求变更出来了,可修改的预置车仍然是10%,只不过可以选择修改路径或者是修改时间,二选一。所以还是可选择修改的变更。我们之前已经完成了自动调参算法,所以不到10分钟,我们就得到了一个有效成绩为2600多,随后分析时间可调这个变更。我们认为,按照已有的模型,时间调整的意义并不是很大,因为我们是每秒的发车数量是确定的,做时间上的调整只不过是交换了两辆车的发车时间而已,总的调度时间不太可能得到优化,因此我们把目光集中在优先车上面,如果最后一辆优先车是预置车的话,并且把它的出发时间往前挪动,可能会优化不少。但是很不幸,官方给的地图中,加上我们的调度模型,最后一辆到达的优先车基本都不是预置车,所以效果基本都不怎么明显,没有把握住这个变更的机会,最终以全国13的名次止步在16强,无缘奖金。

后来与其他队伍交流他们的模型,成绩比较好的都是控制路上的车量数来控制死锁,这样动态规划。这样一来就能够使得路网在整个时间段都比较均衡,而不会出现某个时间段路上车少或者车多的情况。后来官方也给我们分析了我们调度的曲线,每个图都有一个时间段,路上车辆相对较少,所以比较吃亏。后来仔细想过这个问题,确实是之前考虑不够周到,因为每秒发车N量就会导致可能这一秒的车路程比较长,而另外一秒的车辆路程较短,这就会导致路面上车辆随时间的不均匀分布。我们的模型要想解决这个问题也并非不可能,自需要避免某些较短(较长)路程在集中的时间点发车即可,用一个简单的排序即可实现。例如:路程分别是1,2,3,4,5,6,7,8,9,要分配到3个时间片,按照之前的分配:

第一秒:1,2,3;

第二秒:4,5,6;

第三秒:7,8,9;

显然是不合理的,但是如果按照以下:

第一秒:1,6,7;

第二秒:2,5,8;

第三秒:3,4,9;

这样一来就会合理很多,这个时候每秒发车数量N也会增加不少。

最后,还是要感谢华为公司的比赛,让我们有机会展现自己,虽然没有获奖吧,但是让我见识到了与王者的实力上的差距,可以说是心服口服,没有遗憾。另外深圳3日游还是很不错的,包吃包住各种没有花一分钱,还有漂亮的小姐姐带队^.^。

你可能感兴趣的:(DJ,C++,深度学习)