2020华为软件精英挑战赛历程总结——初赛篇

前言

去年懵懵懂懂,一个人从头自闭到尾,到最后也没对上判题器,复赛第十遗憾离场。今年的开端也是十分不顺,我们提交的第一发线上14s,这时候前排已经有0.x的成绩了,一度陷入深深的自我怀疑之中。好在队友十分carry,多核输出的感觉真的挺爽,后来慢慢找到节奏,一路从二十多名到霸榜、守榜,拿下成渝第一、全国第三的初赛成绩,这段经历甚至比之后的决赛更加有趣。

开源地址:https://github.com/Chadriy/CodeCraft2020


放题

一、 题意描述
通过金融风控的资金流水分析,可有效识别循环转账,辅助公安挖掘洗钱组织,帮助银行预防信用卡诈骗。基于给定的资金流水,检测并输出指定约束条件的所有循环转账,结果准确,用时最短者胜。
分析一下,数学描述就是在一个有向图里找简单环的问题。
二、输入信息
输入为包含资金流水的文本文件,每一行代表一次资金交易记录,包含本端账号ID, 对端账号ID, 转账金额,用逗号隔开。

  • 本端账号ID和对端账号ID为一个32位的无符号整数
  • 转账金额为一个32位的无符号整数
  • 转账记录最多为28万条
  • 每个账号平均转账记录数< 10
  • 账号A给账号B最多转账一次

三、初步思路
很自然的,意识到这个题目的核心是搜索,所以朴素DFS即可完成任务(万万没想到这就是官方的std),最后对所有结果进行去重和排序。
由于搜索中存在大量冗余,而且输出中环路按字典序输出,可以联想到去重和排序并不是必须的。考虑1->2->3->4->1的一条环路,在以2为起点时,同样有等价的环路2->3->4->1->2。在搜索中,这两次是冗余的。同理可推导知:在搜索过程中,邻接点中只需要遍历比起点id大的节点,这样即可省去去重;在搜索前对邻接表排序,即可省去排序。
这两个朴素的trick加上DFS就是我们的baseline,线下我记得有一个100w和一个300w的数据,我们前者都跑了好几十分钟的样子- -。之后陆续找了一些论文,都没有找到除了搜索之外的思路。

正赛

一、失落的开局
隐约记得放榜那天,第一发跑了14s的样子,那时候榜单前排已经有0.x级别的选手了,之后是深深的挫败感。正当我们几个人以为今年又是一轮游的时候,小白兔队的martin在知乎上开源了一个base。(开源的一般都很朴素吧),抱着这样的想法我们拉下来,跑了2.x。
心情复杂.jpg。
好累呀要不要弃赛。
如果今年还是solo的话,我可能就这样战术转移了。但是今年队友蔡总十分carry,关键时刻稳住了心态。在开源base中提到了反向搜索的概念,由于环路的起点终点重合,因此可以建立反向图,并通过反向搜索1-2层来替代正向搜索的最后部分。
在此后的很长一段时间里,我都以为正向搜索与反向搜索是数学等价的,区别只在于实现。此时我们的DFS是递归写法,大数据集很容易就爆栈。就在这样的不安中,我们把反向加到了两层,线上几次二分达到了2.x的分数。
这时候魔法少女出现了,同时把上限拉到了0.2x,之后的一周里我们都是深深的懵逼。就这样初赛过半,我们仍然没有进0.x。
在这里插入图片描述
二、开始冲分
此时我们距离前排仍然相差一个数量级,但是我隐约感觉到,这波能打。我们全面进入状态,一点一点把STL的东西去掉,同时把反向拉到三层。第三周,我们每天提升几名,一步一步接近了前排的分数,此时赛区内也出现了0.4的大佬。这个过程中,我们更像是一个天资很差的孩童,在深夜、在天明时分以勤补拙。
tricks:
①、边搜边建反向表
②、路径复用
③、intToString优化
④、反向三层搜索,并存储中间结果
当然这其中还有很多琐碎的小优化,不过与其说优化,不如说是对之前的补正。每天靠着这些微小的努力,我们在周五进到了赛区前排。而此时的上限也拉到了0.1x。
在这里插入图片描述
这其实是一件很残酷的事情,当你发现你奋力追赶到前排,大佬:就这?然后一个二分就拜拜。于是我们仍然未能摸到大佬的屁股。
三、直呼Magic
到了0.4x后,开始上多线程提分,由于最终结果需要按字典序输出。因此多线程的负载均匀成了一件很麻烦的事情,一开始我们用的均匀分片,于是四线程只提了0.1的样子。苦思冥想,在某日去汉堡王恰完饭的路上,跟同学聊着聊着,他提到了分片取任务的方法。
事实上,这种抢占式的方法之前也有想过,但是“字典序输出”这个限制将我卡的死死的。我受到启发,将每个线程开一块独立的内存,然后分片抢占任务。片内有序,片间无序。因此每片不需要按序保存,哪个线程取完任务,跑完一片存到它那块内存中,并记录长度与首地址。在最后输出时按片序输出即可。
2020华为软件精英挑战赛历程总结——初赛篇_第1张图片
当我兴致冲冲提出我的想法时,队友已经通过暴力调参,从0.28调到了0.21- -。这时候队友想到了路径复用的trick,一波操作上到了0.18,我加上了抢占调度,终于挤到了0.17x,冲到了榜一。
此时原来的榜一大佬早已对比赛失去兴趣,继续打ACM去了,我们则开始了一周的守榜。
四、结束与尾声
由于成电单独设置了校内赛奖励, 因此成渝赛区的初赛竞争尤为激烈,各路老sleeper开始浮出水面。这时候我意识到交流的重要性,加到了热身赛的family群中。一开始我只是抱着打探情报的目的,并没有说过话(后来天天水群-qwq),这时候群中经常性会有关于数据集的讨论,“线上数据就nm离谱”让我也困惑了很久。
之后陆陆续续又加了一下小优化,并做了一些地址对齐、异步io、neon加速、调线程片数等尝试,开始研究pagefault的机理等等,总之又优化到0.15。
2020华为软件精英挑战赛历程总结——初赛篇_第2张图片
某晚,深夜12:20。按惯例我们等着蔡总每晚的稳健优化,没想到他直接甩出一个magic。把memcpy的长度改为固定的16位,提升了0.02s。原来我们队也有一位魔法大师。就这样我们一路领跑到了最后,途中在群里看到一个红毛头像,一看这不是伊苏嘛,顺手就加上了。本以为和我一样是个二次元five,没想到是粤港澳榜一的空佬,可能这就是缘分吧。最后一天,我从空佬py到了线上数据集的trick,真nm离谱,提到了0.11x。
最后一天,此刻蔡总与凌少之间师兄弟经过了相爱相杀,无间道影帝之间的相互对戏,我们还是守住了榜一。就这样,初赛结束了。或许我们是那样的普普通通,碌碌无为。但在那一刻,我切实感到了自身的欢喜,在小小的自我世界里,不受纷扰,渺小如我也享受着片刻的安宁。

PS:
1、反向搜索提速的原因是根据meet in the middle定理,搜索复杂度降到了根号N;
2、memcpy16字节提速的原因是,开O3被编译成仅两条汇编指令,走cacheline;
3、仅靠逻辑和底层,提升了100倍让我至今感觉很魔幻;

你可能感兴趣的:(2020华为软件精英挑战赛历程总结——初赛篇)