现在的位置: 首页 > 综合 > 正文

ACRush 楼天成回忆录

2013年09月21日 ⁄ 综合 ⁄ 共 15809字 ⁄ 字号 评论关闭

利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾。首先是 GCJ2006 的回忆。

Google Code Jam 2006

一波三折:

Google Code Jam 2006 是我第一次到美国参加现场的程序设计比赛。 Google Code Jam 2006 的比赛地点设在了纽约,这次纽约之行之前的签证出了不小的问题,这里非常感谢大家对我们的关心,特别感谢吴总( wyy )和鲁小石的帮助,使我最终踏上纽约之旅。

从北京到纽约的飞行时间是 13 个半小时,由于是第一次做超过 8 小时的飞机,没有什么必要的经验和准备,路途非常疲劳。一到宾馆就睡了,结果由于手机铃声的时间使用的是东方时间,差了 12 个小时,一觉把所有事情连晚饭一起都睡过了,随便吃点东西就继续睡了。之后的所有现场比赛我都养成了提前睡觉的习惯,以保证充足的体力。

比赛过程:

比赛时精神状态还算可以,但是分配了比赛房间之后发现自己和 tomek 分在一个房间,真是很不爽;在和旁边的 zhuzeyuan 抱怨的时候,发现他和 Petr 一个房间,彼此彼此吧。

下面就是比赛过程了,总体来说比赛过程比想象的艰苦,不过其实在 System Test 之前的结果还是很满意的,先简单描述一下 3 道题目吧。

250 分的题目是一道平面极值问题,给定 n 个点,求一条直线,使得 n 个点到这条直线的 y 方向截距总和最小。我回忆起金凯在 2003 年集训队论文中报告中讲到的很类似的一道题目,记得一个重要结论是这条直线一定经过两个点,虽然题目有些不同,但是很快得到了相同的重要性质:这条直线一定经过两个点。这样很容易得到一个 O(n3) 的算法

500 分的题目是一道反 Hash 函数问题,给定一个 Hash 函数和 x ,求一个最小的非负数 y 使得 H[[yes]]=x
。估计了一下,单向搜索需要 26^8 ,于是我改用双向搜索,这样就变成了 26^4 。但是实现过程比想象的复杂很多,提交了后只有 280 左右了。其实,这题有更简单的数学方法, tomek 的程序有 450+ 。

1000 分的题目是涉及卷积函数和计算反函数的问题,通过转化变成线性方程求解问题。当时受到现场气氛的影响有些紧张,浪费了不少时间,提交之后 550 分左右。其实,当时一些原理问题都没有想清楚,不过后来和 Ying (王颖)经过讨论验证都是正确的。

Coding 结束之前 Petr , tomek , Ying 和 andrewzta 都提交了 3 题,其中 Petr 领先得比较多,我和其余 3 人差距 50 分以内。

Challenge 阶段开始之后,我由于 500 分题自己使用的是双向搜索的算法,没有注意到有些单向的搜索加模线性方程的方法其实是正确,在 10 分钟以内 cha 错了 2 次。落后于上述的 4 个人,排在第五。

但是下面的 5 分钟发生了戏剧性的一幕,首先是 Petr 的 250 被 cha 了,接着 Ying 的 250 也被 cha 了,这样我面临这样一个情况: tomek 领先我 100+ 分, andrewzta 领先我 30+ 分,由于我和 tomek 处在一个房间,所以我做出了一个大胆的决定,就是 challenge tomek 的 1000 分题,我随机生成了一个随机大数据,在最后时刻提交了这个 challenge ,系统返回了一个令人窒息的结果:
successfully challenge 。凭借这 50 分我一举超过了 tomek 和 andrewzta ,在 System Test 之前占据了榜首的位置。

戏剧性的结果:

我很有幸能够在第一次参加现场比赛时,就能够和冠军这么接近,如果 System Test 能够全部 Pass 的话,这可以认为是一场完美的比赛。

可是,整个故事就好像是被刻意设计的一样, System Test 之后的结果使我目瞪口呆:首先是 250 分的题目,我由于有一个地方没有及时使用 double ,而造成整数越界;然后, 1000 分的题目简直是悲剧的最高境界,我在高斯消元的时候没有及时把一个重要变量暂存,导致影响了结果,没有想到竟然躲过了那么多大数据,但是不能通过 System Test 。最后排在 50 名左右。这两个错误至今刻骨铭心。

最终 Petr 获得冠军, Ying 亚军, andrewzta 由于 500 挂了排在第 3 。

11 月的纽约有些冷,我随大队人马一同去了一趟帝国大厦,景色很迷人。第二天休息一下后与几个中国选手打了一会 “ 找朋友 ” ,第一次美国之行就结束了。

总结:

比赛结果虽然不是很理想,但是对于第一次参加世界比赛的我还算可以接受。也算是为今后的比赛留下一些教训吧。

在帝国大厦上见识了大家的拍摄功底,我由于技术差没有拍到任何合适的照片。

在比赛过程中,首次见识了 liympanda 的大将风度,和 panda 在一起总是笑口常开,他无论遇到什么情况都无所畏惧,这一点我一直在努力学习,不过一直做的不好。但是 panda 打牌的时候就不一样了,总是喜欢偷看别人的牌,还炫耀自己会说广东话,被 Ying 和 rocking 两位广东选手狠狠鄙视了一番。

Petr 加上之前的 TCO 和之后的 TCCC ,拿到了 2006 年的大满贯,可以算是历史性的突破吧。 Tomek 有些可惜,比完了还问我怎么 cha 他 1000 分的,呵呵。

其实这次比赛 Ying 挺可惜的,其实 Petr 的发挥并不很好,如果 Ying 运气再好一些的话,历史从那时就要重写了。不过 Ying 还是体现了他超强的数学功底,让人佩服。另外,来自复旦的同省队友 LemonTree 也获得了好成绩。

这好像是自己最后一次和 xreborner 同场竞技了(由于之后 xreborner 退役了很长时间,忘记 GCJ2008 我们又见面了,谢谢 Savior 的提醒),感谢您在我高中时期教授了我许多编程技巧,我一直沿用至今。

利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾。昨天是 GCJ2006 的回忆,今天时间上更早一些吧,我现在还清晰记得 3 年前,我刚刚参加 ACM 时参加北京赛区 2005 和杭州赛区 2005 的情况。

2005 年 ACM-ICPC —— 酸甜苦辣

我进入清华大学开始本科学习的时间是 2004 年 8 月,在进入清华大学的第一年里,由于基础课学习比较紧张,再加上计算机系不允许大一学生自带电脑,我没有参加 2004 年的 ACM 比赛。不过在大一一年中没有停止这方面的练习,对 ACM 还是热情高涨。

大概在 2005 年 7 月底,与同班同学 shell (贝小辉)和 superzn (张宁)一起决定组队参加 ACM 比赛。对于队名没有太多的想法,就随便取了一个字典序靠前一点的 bomber 。随后进行的几场训练中,我的编程状态一直保持得很好,训练比赛的主要方式都是:我主写程序, shell 和 superzn 负责翻译题目,思考算法和测试。这种组队模式一直沿用到我们后面的所有比赛中。

2005 年底,我们报名参加了 2005 年的北京赛区和杭州赛区的比赛。顺利通过了预赛进入了现场决赛。记得当时北京赛区预赛的时候,我和 superzn 一起在参加百度之星程序设计大赛, shell 依靠一人之力过了 6 题,最后以第二名的资格参加北京赛区现场比赛。

北京赛区:

2005 年的北京赛区地点设在隔壁的北京大学,由于交通非常方便,我们没有和大部分选手住在一起,不过也没有参加 Java-Challenge 和晚上的表演。

练习赛之前,说到比赛位置抽签,本身意义不是很大,可是邬老师神奇的 RP 把两只清华的队伍抽在一起,结果练习赛进行了一半,另一只清华的队伍 THU1 (队员是:吴景岳,栗师和金凯,好像后来队名改成了 DreamCatcher ,不是很确定)被要求换到一个比较远的地方,理由是有些学校觉得这样不合理。后来很多赛区也出现过队伍座位在一起的情况,邬老师的 RP 果然不是盖的。

记得练习赛时和复旦的 LemonTree (盛城)一起在场地里闲逛,结果果然不到 10 分钟就被要求回座位了。还有当时比赛场地是一个体育馆,有些队伍把气球放飞之后气球就飘在天花板下了,总裁判李文新老师还威胁我们说,如果明天正式比赛把气球放飞,就不算通过相应的题目,除非有办法把气球取下来。

然后就是比赛的过程了,下面有底纹的文字是我找到的当时留下的比赛总结:

E :快速排序。 5 分钟 1Y 。

我想 5 分钟的时间可以争取这几年 ACM 国内赛区的最快出题记录了吧。

G :二分答案 + 最小生成树。 25 分钟 1Y 。

这题就是经典的最优比例生成树问题,我们一致认为这题比较简单。不过后来被李文新老师批评了,说法是误导其他的队伍。不过说到最优比例生成树问题, TCO2006 的时候 fwj 和 tomek 竟然都没有见过这道题目,这题可是源于 POI 呀。我想我们认为这道题目简单的主要原因是我们都在冬令营上见过这到题目,如果第一次看见,想出算法可能确实需要一些时间。在这里向被我们影响的队伍的道歉,最终 G 提交了 200 多次,但是只有 8 个队伍 AC 。

C :二分图最大匹配。 42 分钟 1Y

题目要求计算一张图的最小覆盖集,可能唯一的 tricky 是发现图是二分图。

D :遇到了一定的困难,发现 A 很简单,于是先放一下

D 是一道比较综合的题目,设计一些简单的计算几何和字符串处理的知识。

A :简单的几何问题,出现了一个低级错误,提交了 3 次均为 WA 。

A 是北京赛区最简单的题目,我的程序里犯了一个很低级的错误,可能也是经验不足造成的吧。

D :重新写,但是没有考虑一种情况, WA 了 1 次。

87 分钟,复旦的 Abuacus 过了 4 题占据了 Rank1 。由于队伍模式的原因,我们在还有很多简单题目的情况下卡住了长达 30 分钟。

A : shell 突然发现了 A 程序中的低级错误, 105 分钟 AC ,重新夺回 Rank1 。

这是很重要的一步,现在想来如果没有这个发现,后果可能不堪设想。

B :二分答案 +2SAT 。 129 分钟 AC 。

B 是一道明显的 2SAT 问题,由于题目比较长,我们没有很早发现这道简单题。

D :发现了 D 的没有考虑的情况, 140 分钟 AC 。

看了一个 board ,那时 Abuacus , Eccentric 都只有 4 题,能够在第一次参加正式比赛就做到 6-4 的领先,当时心情很激动,不过由于缺少经验,也影响了接下来的发挥。其实,现在回想起来,这次比赛其实是一个很好的 AK 的机会。

F : DP 。程序比较复杂, WA 了 4 次。

F 是一道比较复杂的动态规划的题目,其实 WA 的原因是一个应该用 int64 的地方,我们使用了 int ,这个地方的确很难发现。

H : F 一时无法 AC ,只好转功 H 。 H 就是普通的模拟题。开始没有考虑坦克和炮弹可能在 1/3 秒相遇, WA 了 1 次。

比赛还有一个小时,封板。

H : shell 发现了坦克和炮弹可能在 1/3 秒相遇的情况, 250 分钟左右 AC 。

对于我们这种组队模式,当主写程序的选手状态不好的时候,很容易出现连续卡题的情况,这种情况的出现很不利于水平的正常发挥。在北京赛区的比赛中,我们很有幸没有出现连续卡处的情况。

记得,当时北京赛区的 Judge 的半自动的,就是说如果结果是 AC ,速度就会非常快,否则由于人的介入,不能 AC 的提交往往需要等一段时间。我们第 2 次提交 H 之后,没有得到很快的回复,以为已经 WA 了,于是我和 superzn 继续测试一些数据。但此时,突然有一个 mm 从左边走过来插气球,这个气球也成为了全场唯一的蓝色气球,这个意外之喜最后成就了第一个分区赛冠军。

F :下面就是痛苦地提交 F ,一直战斗到最后一刻, WA 了 14 次,留下了北京赛区最大的遗憾。

在最后时刻我们似乎发现了那个 int64 的错误,不过当时思路已经比较混乱了,没能改对。 F 的问题也导致没有时间写 I ,当时如果直接重写后者换 superzn 来写 F ,完全可以在比赛结束前 AC 。

比赛的大致过程如上所述,那个神奇的气球,我现在仍然记忆犹新。最终有 4 个队伍攻破 7 题, Abacus 的组成应该是盛城, timegreen 和 suzhan 吧, Eccentric 中我只记得辛韬, ZSU_Panku 中我记得 Savior (陈实)。上述的老朋友之后见面的机会就很少了,分区比赛也成为了我好需要老同学重要的交流机会了。

我 ACRush 的 ID 估计就是那时开始使用的吧,转眼就已经 3 年多了。

比赛前后还记得经常与复旦大学的吴永辉老师聊天,在那之后的每次比赛我都能见到他年轻的身影。

现在回想起北京的分区赛,很有幸能够在第一次参加 ACM 正式比赛就获得分区比赛的冠军。我想是由于现场气氛对许多队伍都有不小的影响吧,当时许多队伍都卡在几道比较繁琐的题目上了,题目的算法性都不是很强。我大概从那时才刚刚接触 TopCoder ,如果能够早一些,相信会更适应这样的比赛。

杭州赛区:

2005 年的 ACM 杭州赛区比赛在浙江大学举行,杭州赛区的时间就在北京赛区结束后一周,最初选择杭州赛区的原因很飘逸:我自己家在杭州。实际上也差不多,我随队伍(当时 THU 派了 3 只队伍参加杭州赛区的比赛,除了我们队之外, b142857 (侯启明), zhy (周源), ysy (杨思雨)组队,另外一只由汪汀,王俊和黄源河组成)一同抵达杭州车站之后就马上回家休息了,直到比赛前才赶回。在北京到杭州赛区之间的一周中,我的状态就在不断下滑,在家中完全失去了比赛的气氛,回到赛场再也找不到感觉了。一场悲剧即将上演。我们先看看比赛过程吧,下面有底纹的文字是我找到的当时留下的比赛总结:

G :初看很简单,但是调试了 30 分钟没有结果。

G 是一道数学问题,其实《具体数学》书上有明确的公式,不过我们使用的递推方法应该也可以得到正确的结果。程序中犯了一些低级的错误,由于实在不在状态,调试了 30 分钟还没有找到错误。这里还暴露了一个组队模式的问题,在后来的组队模式中,如果像这样没有想清楚算法的题目队友是一定不允许我去写的。

A :模拟。 41 分钟 AC ,当时肯定没有想到这是唯一一道 1Y 的题目。

A 是一道模拟题, 1Y 的时候已经很晚了,排名也很靠后。

C :图论。但是由于堆栈逸出 RTE 了 5 次,浪费了大量的时间。

C 的问题关于树中祖先关心的判定,题目很简单,实现的方法也很容易,就是通过一遍 DFS 来计算。但是我们忽视了一个从来没有遇到过的问题:堆栈溢出。而且,堆栈在本地机器上运行过程中, Eclipse 提供了 8MB 左右的堆栈,所以没有溢出,但是在提交之后的环境下运行就溢出了。而且每次 RTE 之后,我们一直在尝试修改数组的大小,一直没有找到根本原因。调试 C 的同时,我也尝试修改 G ,结果 G 也错了 8 次之多,并且始终都是 WA 。

I : shell 在我郁闷地调试 C 和 G 中 AC 了,之前 WA 了一次。

I 是动态规划问题, WA 一次可能是忽视了一些边界情况。

D :网络流,没有想到先贪心进行优化。 TLE 了 5 次最终没有通过。

D 就是计算最小割,我们事先准备了先流推进算法,不过根据这道题目的模型,先流推进算法遇到最坏情况:二分图。由于当时 dinic 还不是很流行,我们 TLE 了 5 次还没有通过。

郁闷地调试 D 和 G 。

E,B :都尝试过,但是都出现了不明的问题。

在随后的时间里,不断调试 D 和 G ,但是始终不能 AC 。之后又尝试 E 和 B , E 通过分段的方法可以处理, B 是数学题目。正常的话 E 和 B 并不是很困难的题目,但是当时已经非常混乱,连样例都没有通过。

最终我们只过了 3 题,排在 21 名,经历了我参加 ACM 以来最惨痛的失败。这次失败主要归过与我状态太差,基本上什么题目都不能顺利通过。当然题目的选择也有很大的问题: G 确实不是难题,但是由于未知的原因始终不能通过,后来我把纸上的程序敲在 ZJU 上就 AC 了,至于现场为什么不能 AC 我现在还是不能明白。如果说第一题的选择直接影响了我们的信心,那么 D 的堆栈溢出则完全打乱了我们的节奏。对于我们的组队模式,卡出 2 题已经超出了极限,我们不可能再尝试其他题目。

Abacus 也来到了杭州,他们前期体现了强劲的先期优势,在 2 小时就达到了 6 题; b142857 (侯启明), zhy (周源), ysy (杨思雨)的队伍表现得相当神勇,在最后一小时超越了 Abacus ,夺得了冠军。

杭州赛区的失败至今仍是心中痛苦的回忆,不过这个教训也是对我今后的学习生活的一种警示。

总结:

2005 年是我第一年参加 ACM-ICPC 的比赛,两场 ACM 分区赛,我们经历了夺冠的兴奋,也经历了环顾四周等待比赛结束的无奈。 2004 年清华没有获得任何分区赛的冠军, 2005 年清华打了个漂亮的翻身仗,先后在成都,北京和杭州夺得冠军,而且是三支不同的队伍。

两个赛区的 G 都是有传奇色彩的题目。北京赛区中,我们 25 分钟 1Y 了 G ,导致许多队伍跟风失败,最终达到了 208 提交 8AC 的低通过率。但是,杭州赛区中, G 从比赛一开始就占用了我们大量的时间,直到最后都没有通过,估计至少浪费了两个小时左右。真所谓成也在 G ,败也在 G 。

北京赛区后, POJ 的论坛上传闻说我曾经说过 “ 起身去厕所,不许碰键盘。。。 ” ,很敬仰那些同学搞笑和扯淡的功底,我们虽然定下了以我主写程序的组队模式,但是也非常重视配合和每个人在队伍中的重要作用。

当时清华没有组织校内 PK 选拔,选择了成都赛区的冠军队 THU1 参加全球总决赛,当时总决赛队伍是以参考第二赛区的成绩决定的,现在回想起来也是很合理的。由于最终我们未能得到机会参加全球总决赛,接下来几个月我们情绪低落, bomber 从那时也就宣布解散了吧。

2005 年的比赛过程中,我见到了许许多多的老朋友。用吴永辉老师的话, ACM 竞赛可以看作一些老朋友一起进行的一场智力游戏。

利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾。回顾了 GCJ2006 和 2005 年的 ACM 之后,转向 TopCoder 的比赛吧。我参加的最早的 TopCoder 赛事是 TCCC2006 。

TCCC2006 —— 死亡之组

TopCoder Collegiate Challenge( 简称 TCCC) 是 TopCoder 一般在秋季举行的面向全世界在校学生的程序设计大赛, 2006 年的 TCCC 在圣地亚哥举行。从北京到旧金山的飞行只需要 11 个小时左右,所以不至于那么疲劳。路上一切都很顺利,很感谢 OpenGL 的提醒,对于超过 8 个小时飞行自带拖鞋和枕头对我来说还是很重要的。

TCCC2006 使用的标准的 TopCoder 现场比赛形式,比赛有 48 名选手参加,首先 48 名选手被分为 16 个人一组,每组分别进行半决赛,前 2 名直接晋级决赛, 3-6 名晋级 wildcard 比赛, wildcard 比赛 12 人中的前两名填补决赛的最后 2 个名额,决赛由 8 个选手参加。 TopCoder 现场比赛中很重要的一个创新是:每名比赛选手在观众席前都有一个同步的显示器,这样观众可以看到选手任何时刻做的事情,极大增强了互动性。

TCCC2006 的 Room 1 和后面的 Final Round 都可谓是死亡之组。现在就回忆一下这两场激烈的比赛吧。

Room 1 :

至于 3 个房间的分配, TopCoder 按照注册截止时选手的 Rating 分布蛇形分配。但是 TCCC2006 的房间实力分布极不平衡,我与上届冠军 tomek ,著名选手 reid , Egor , halyavin 还有 Rating 不高但是实力极强的 Ying 和 ardiankp 同被分到了 Room 1 ,赛前 Room 1 成为公认的死亡之组。

在圣地亚哥,我和师兄 Macsy (张一飞)同一个房间,很感谢师兄的关心,我那几天休息的都很好。要知道如果同房的人有 10 小时左右的时差的话,一人必须很小心才能保证不影响另一人的休息。

Room 1 在我抵达美国的第二天早上进行,选手允许提前 30 分钟准备一些必要代码。不过现在大家都比较喜欢学习 Petr 那样一行代码都不打。下面就是比赛的过程:

250 分题目是:给定 n(n<=50) 个整数 AI 和一个阈值 d ,计算 n 个整数所有排列 PI 中满足 |API-API+1|<=d 的排列中,所有不同可能 AP1 的个数。这题最标准的方法是动态规划,基本思想是把 n 个整数排序之后,计算两条相邻元素不超过 d 的序列。我使用了一种更精巧的算法,把 n 个整数排序之后,对于 AI ,如果
AI 可能作为排列的第一个元素,那么 AI 必定在某一个方向(大小)连续而在另一个方向每间隔两个元素相连。这个算法比较容易实现,但是正确性证明比较难,甚至让人第一感觉是错的。我写完程序测试了所有样例都正确就提交了, 243 分。提交之后我又测试了许多数据,并在纸上尝试证明正确性。

赛后,我看了网络上的讨论记录。在我提交 250 分题之后,立刻遭到了 misof 的怀疑,他认为我的算法有问题。据 Macsy 学长的回忆, OpenGL 在我屏幕前看我写完程序,也认为我的算法是错的,不过后来他们讨论之后发现没有反例。

关掉 250 分题目之后,我刚刚意识到 Room 1 的 3 题分数不是 250-500-1000 ,而是 250-600-900 。现在看来,对于 250 比较顺利的情况,应该先做 500 ,若 250 不顺利或者想出奇制胜的话,可以先开 1000 分。当时没有什么经验,我认为 900 比 600 应该简单一些,于是就打开了 900 。

900 分题目是:给定一张 n(n<=10) 个点的带权有向完全图(也就是 n2 个实数)和一个衰减系数 p ,求一条经过 d(d<=10) 条边路径(不需要保证简单路径),要求这条路径的指数衰减长度(指数衰减是指第 i 段的长度乘以 pi-1 然后求和)最接近 1000 。这题如果使用穷举法,就需要 1010 左右的计算量,在 TopCoder 的测试机上也不能通过,由于路径长度很容易超过
1000 ,所以很难找到多项式时间的动态规划。我马上有了一个想法 —— 双向搜索。对于长度为 d 的路径,其实可以看作从某一个点 p 出发的一条反向的长度 [d/2] 的路径和一条正向的 d-[d/2] 的路径,对于固定的节点 v 来说,这种两个方向的路径都不超过 n[d/2] ,这样只要枚举一个方向的路径然后二分查找另一个方向即可。复杂度是 O(dn2+[d/2]) 。

现场比赛调试环境不是很好,我花了不少时间调试以发现程序中的错误。提交之后 690 多分,还不到 700 。不过对于 900 分的题目在那种压力下还可以接受。提交之后我花了 15 分钟左右测试,没有发现错误。于是就准备做 600 了。

600 分题目是:一道经典的数学题,给定一些盘子叠放的规则,计算顶层盘子的最大可能大小。其实算法不是很难,只要二分顶层盘子的大小,然后依次贪心计算来判断底层是否能够满足即可。只是贪心的时候要考虑两种情况,一时想不清楚。我当时已经感觉很疲劳,思路不是很清楚,最后 40 分钟时间也没能调试通过。这题过于琐碎, Room 1 中最终没有选手通过 600 分题,并且成就了一个刺激的 Challenge 阶段。

Coding 阶段我和 tomek 采用了截然不同的策略,我跳过 600 直攻 900 ,而 tomek 在 600 中挣扎了很长时间才放弃。 Coding 阶段结束时,有 4 名选手提交了 3 题。我依靠速度优势领先同样提交 250 和 900 的 tomek 35 分左右。

Challenge 阶段开始时,我盲 cha ( blind challenge )了一个最后时刻提交的 900 分程序,但是由于我选择的数据实在太弱,失去了 25 分。这样我和后面的 tomek 只相差 10 分左右了,所以我决定只要 tomek 不动,我也不动了。其实,当时 tomek 已经知道自己的 900 是错的, Challenge 阶段他估计已经放弃了。我的 Challenge 阶段最终就以 -25 分结束。

之后的 Challenge 就是 Ying (王颖)展现勇气和智慧的舞台了。他 Challenge 掉了所有提交的 600 ,凭借 225 分的加分超过了我,排在榜首。这样比赛的形式也一目了然了, 7 位选手提交了 900 ,我依靠速度优势领先第四名 reid 超过 100 分。只要我两道题目能够 Passed System Test 就足以进入 Final Round 了。

System Test 之前,我和 Ying 讨论他 “ 超神 ” 的 Challenge 阶段。这是我第一次参加 TopCoder 的现场比赛,发现 System Test 结果显示是按照 System Test 之前的排名倒序进行的。测试到我时,除了 tomek 的 4 名选手的 900 都过了。显示我的结果时,两个绿框闪烁了很久终于显示出了两个大大的钩,我终于可以欢呼庆祝胜利了。我前面的 Ying 也两题全过了。这样我们两位中国选手得以在死亡之组携手出现,这场比赛真可谓是中国选手的胜利。
Reid 只能在 Wildcard 赛再作努力, tomek 则被直接淘汰出局了。

Final Round :

接下来的两天里,我观看了 Room2 , Room3 和 Wildcard 的比赛。第 2 天晚上我们参加了 TopCoder 赞助的 Laser Tag 游戏,我们所有中国人组成了一队,我的发挥很差,原因是这个游戏与 CS 不同,选手头上没有感光器,而我喜欢遇到人就攻击头部,所以狭路相逢多半是我失败。活动中,我有幸结识了许多 Dev 的神人,当时由于 vividmxx 没有参加, magicpig 和 PE 的竞争很激烈,最终 PE 获得了 “ 浙江大学建校
100 年来第一个 TCCC 冠军 ” 。记得赛后我 uncle 来到现场,我 uncle 是浙江大学本科毕业的, magicpig 见我 uncle 第一句话就是 “ 浙江大学建校有 100 年历史了吧? ” 汗死了。另外 zjq 也获得了 Design 的亚军。

第三天中午 Championship Round 开始了。决赛时,场地里安装了很多摄像头,可以说我们的任何举动都在严密监视下了。这回我提前确认了题目分数是标准的 250-500-1000 的分布。参加决赛的选手除我之外有: andrewzta , ardiankp , bmerry , Eryx , mathijs , Petr 和 Ying 。面对决赛选手的实力,我已经没有意义定一个类似于 “ 保几争几 ” 的目标了,努力发挥自己的水平是最应该做的。下面就是比赛的过程了:

250 分题目是:给定 n 个正三角形,每个顶点都有数字,选出 6 个三角形拼成一个正六边形,要求相邻的数字必须相同。三角形允许旋转,计算能够得到多少个本质不同的正六边形。题目很长,我仔细读了两遍才开始写,算法很清楚,就是枚举六边形中心和四周的 7 个数字,然后判断是否有足够的三角形。在判断本质不同的时候犯了一个错误,调试了几分钟,提交之后只有 215 分了,看了一下排名, Petr 有 232 分之高,其他选手都还没有提交。测试了几分钟发现程序的运行时间不是很稳健,很容易到达
0.8 秒左右,测试了 15 分钟之多才逐渐放心下来,因为基本上所有数据都 0.8 秒左右。赛后 Macsy 告诉我,我的程序速度瓶颈是在 set 的判断,所以时间比较稳定,不会超时。我当时的犹豫和没有经验浪费了至少 20 分钟的时间。

按照赛前的计划,我这时应该打开 1000 的题目的,但是由于自己对 250 没有信心,而且求稳思想比较重,我先打开了 500 分的题目。现在看来,开 500 分的题目并不算错误,其实在打开 500 分题目的时候,与 Petr 的差距不是很大。

500 分题目是:给定一个机器人的移动命令序列,要求计算结束时机器人的位置。由于移动序列中允许 () 这样的重复操作,直接模拟是超时的。这类题目的标准算法是利用矩阵乘法,由于之前对于此类题目没有经验,没有准备好就开始写了,导致矩阵处理失败。我果断放弃了调试,换用一种记录中间结果的搜索方法,写完的时候已经只有 280 分了。更重要的是我已经没有时间进攻 1000 分了。提交之后排在第 3 ,前面是 Petr 和 Eryx 。

1000 分题目是:给出一个排队取菜的模型,计算一个等待时间的排队序列。而且对于多种答案的情况,要求计算字典序最小的序列。题目其实不是很复杂,集合动态规划就可以解决,不过模拟取菜过程时需要非常注意细节。 Petr 提交了一个 660 分左右的程序, Ying 则在最后一分钟提交了 400+ 分,排在第 2 。

Challenge 阶段显得很枯燥无味,前两天大发神威的 Ying ( +225 )和 Petr ( +300 )都没有尝试 Challenge ,整个 Challenge 阶段没有任何一个 Successful Challenge 。

System Test 结果出来了,在 bmerry , ardiankp 和 andrewzta 都只通过一题的结果出来之后,排在我后面的 mathijs 两题都 Pass ,随后我的 250 和 500 也都 Pass 了。但是,排名在我之前的 Eryx 和 Ying 的 500 分和 1000 分都 Failed System Test 了,我瞬间提升到了第二名的位置。不过虽然 Petr 的 1000 分挂了,但是他依旧凭借 250 和 500
的速度获得了冠军。

在这里说一下 1000 分的真实情况吧,因为这些时间来对于 TCCC2006 Final Round 的 1000 分题目有很多不同的说法。比赛结果中显示没有选手通过 1000 分题,如果仔细分析测试结果, Petr 的程序由于超时出错,而 Ying 的程序由于一个地方没有清 0 而导致错误,确实很可惜。因为如果 Ying 的 1000 能够 Pass 的话,他将是 TCCC 的冠军。不过 Ying 的算法犯了与造成 Petr 超时一样的错误,他们的动态规划程序比标准方法多出一个
n 倍的时间,我曾经成功生成了一个用例,可以让 Ying 和 Petr 的程序都超时,这个例子已经得到了 Ying 的认可。需要指出的是 TCCC2006 是 TopCoder 的测试机的速度还是很慢的,两个程序如果在现在的机子上运行可能只需要 1 秒左右了。

比赛之后和 uncle 到 downtown 游玩了一下,参加完颁奖晚会,第二天就回国了。

总结:

TCCC2006 是我第一次参加 TopCoder 的现场比赛,很有幸能够在这么多的第一次中就进入决赛并且获得第 2 名的成绩。很感谢同参加比赛的同学 Macsy , OpenGL , Ying 还有 PMH 的关心和帮助,你们在我比赛时全程在场边,让我感觉很温暖。

另外,我还有幸认识了 visualage ,现在他已经是 arena 的负责人了吧。记得他和 OpenGL 在 Room 1 的 Challenge 阶段通过大声叫中文(在国外,这是最好的密码)告诉我 tomek 的 900 是错的,可惜我没有听见。

TCCC2006 对于中国来说是不小的收获,中国选手占领了 Dev 比赛, PE 获得 “ 浙江大学建校 100 年来第一个 TCCC 冠军 ” , magicpig 和 zjq 分获 Dev 和 Design 的亚军,也就是说中国包揽了所有亚军。在比赛之余,我很高兴认识了众多 TopCoder 的朋友。

Petr 在决赛中表现了非常良好的状态, TCCC 的夺冠标志着 Petr 收获了 2006 年的大满贯。 Ying 也采用了很合理的策略,只可惜他的赌博由于运气差一些惜败。我采用了比较保守的策略,在所有决赛选手中排名第 2 ,这也是我在 TopCoder 的现场赛事中的最高名次了。

TCCC2006 我很感谢家人的关心,父母凌晨很早起床查看我的比赛结果,而 uncle 还特地赶来现场为我加油。这几年的 TopCoder 现场比赛的赞助商列表里都能找到 American Online(AOL) 的身影, TCCC2006 是 AOL 唯一一次进行了 3 个小时左右的全程直播,父母和 uncle 都在网络上观看了现场的影像直播。

TCCC2006 我神奇地保持了 100% 的正确率,我个人认为 TopCoder 现场比赛对正确率提出了更高的要求,我们不必太在意 Coding 阶段的那些高分,只要自己的程序是正确的,就是成功的。

利用假期空闲之时,将这几年 GCJ , ACM , TopCoder 参加的一些重要比赛作个回顾。回顾 GCJ2006 , ACM2005 , TCCC2006 和 ACM2006 之后,今天简要回顾一下国内个人赛场吧。

国内个人赛场 —— 百度之星

国内个人赛场中最重要的比赛要数每年一度的百度程序设计大赛,到今年为止已经举办了 4 届,每一届我都全身心地参加了比赛的全过程,百度程序设计大赛是中国举办的规模最大的公开程序设计比赛,其参加人数比许多世界范围的程序设计大赛的人数还要多得多。另外在 2006 年初, Google Beijing 举行了 Google Code Jam China 的比赛,刚刚开始参加 TopCoder 的我也加入了这次 GCJC 之旅。

第一届 baidu 程序设计大赛:

最早的国内个人程序设计比赛要回忆到 2005 年 9 月开始的第一届百度程序设计大赛了,源于宿舍走廊中的海报,我以尝试的心态报名参加了第一届百度程序设计大赛。每一届百度程序设计大赛都由初赛,复赛和现场决赛组成。

第一届百度程序设计大赛中,印象最深的复赛题目就是那道规模巨大的最小树形图问题了, 100000 的数据规模吓退了不少选手,我鼓足勇气提交了一个理论上能够运行的程序,顺利通过了复赛进入决赛。最小树形图算法在大多图论书上就接在最小生成树算法后面,但是其程序量远比最小生成树大,而且用途没有最小生成树广泛,在大多数竞赛中很少出现。我最早接触最小树形图算法是在 2003 年 4 月,当时正在复旦大学训练,记得关于这个问题和 xreborner 讨论了很长时间才得以证明算法的正确性并实现出高效的程序。

现场决赛于 2005 年 10 月底在北京举行,由于当年比赛的知名度不高,时间上还和 GCJ 冲突,没有太多的顶尖高手参加。清华大学除我之外只有 superzn (张宁,我们留 shell 一个人参加 ACM 北京赛区预赛 L ),当时 OpenGL 还是以高中生身份参加的,还有复旦大学的 xreborner 和 young (李阳);中山大学的 magicpig , Savior 和张子臻(不好意思,我不记得您的 ID 了,好像杭州 2008 的时候我们还说起此事)。我一直认为,现场比赛过程的一个重要的意义在于提供了一个老朋友重逢和结实新朋友的机会,选手之间的交流是比赛中最重要的组成部分之一,我很有幸能够在这些比赛中认识了众多牛人。

稍微回顾一下决赛的题目吧:决赛的题目是经典的 8 数码问题,给定初始状态和结束状态,计算最短需要的转移步数。对于分数相同的情况,按照程序的运行速度排名。比较容易想到的方法有:

(1) 单向 BFS :最坏情况需要 1s 左右。

(2) 双向 BFS :如果先判断无解情况,这是 xreborner 使用的方法,平均情况大概 0.002 秒左右。

(3) A* 或者 IDA* :先判断无解情况,然后通过距离启发函数搜索。平均情况大概 0.002 秒左右。我当时使用了 A* 的方法,但许多地方的实现不是很合理。

(4) 常量表,这是最有挑战的方法,因为决赛的提交量限制在 64K 以内。

现场比赛中, (2) 和 (3) 的使用人数比较多,速度相差无几,选手之间比拼的是各种细节和常数的处理。后来,我想出了一种速度非常快的方法:

首先使用 A* 加上 “ 卡节点 ” 技术,就是限制 A* 算法搜索过程中每层的节点个数上限,这种算法扩展节点个数在 100 左右。然后,由于上述算法的正确性不能保证,把所有反例打成常量,程序大概 50K 左右。很容易发现,这个程序的速度远比比赛过程中所有程序的速度都快得多。

最终我的程序以总时间 0.022 秒获得冠军, xreborner 和 Savior 以 0.026 分并列第二名。 xreborner 的程序很可惜,如果加入了无解判断,速度应该比我程序块, superzn 就更可惜了, superzn 的飘逸程序其实只有 0.020 秒,但是有一个数据错了。

记得颁奖之后,主持人邀请获奖选手发言,选手可以通过向前走一步选择优先发言。这时,我突然感觉大家把目光都聚焦到了我身上,向右一看,由于我站在最左边没有注意到右边的情况,可谁知其他选手都后退了一步,把我留在了看似向前一步的位置。

第二届 Astar-baidu 程序设计大赛:

第二届百度程序设计大赛没有等到 10 月,而是在 2006 年 6 月就拉开序幕。没有想到的是,第二届百度程序设计大赛竟然以我在一年前比赛中使用的 A* 算法的名字命名,感到非常荣幸。

记得复赛的题目非常正式,印象最深的要数 xreborner 招牌式的 Zuma ,我推了两个小时公式才得到了正确的动态规划方程,实现之后还由于 TLE 只有 30 分 (100 满分 ) 。还有 Ying 出的无向图最小割问题,我用网络流算法又超时了。不过最后一题,我的程序竟然比 xreborner 优化过的标程还快,真是不容易呀。清华的舍友 RealPlayer 在复赛中表现很兴奋,可惜由于一个很小的错误没能进入决赛。

参加第二次百度决赛的选手中有许多熟悉的面孔,清华的同学包括 shell , OpenGL , lympanda 还有 Macsy 。复旦大学也来了很多选手,其中除了 LemonTree 和 Topkiller (沈毅)之外,还有我刚到复旦时见过的 admin 和 funny 的身影。另外 magicpig 和 flymouse 也参加了,而且 magicpig 和我住一个房间,吃饭时记得他把桌上所有人的 Dev 功底全都鄙视了一遍,可惜 PE 不在场呀。比赛前还看到了
Srbga 的身影,据他说是被邀请一起来玩的,其实稍微用小脑判断一下就知道一定是参与出题的,有了 Srbga 的加盟,相信决赛题目绝对不会是一年前的风格了。

第二年决赛的题目是:著名的俄罗斯方块。写程序玩一个 10 列的标准 2 维俄罗斯方块游戏。

Srbga 设计了很有特色的记分方法和评分标准。对于记分方法,特别的地方是消去 1 行后没有得分,而同时消去 2 , 3 , 4 行的得分分别是 3 , 6 , 10 ,记分方法非常鼓励一次消去多行。评分标准则更奇怪了,有 50 种不同规模的数据,对于每组数据对所有选手的得分进行排名,前 8 名的选手依次得到 10 , 7 , 6 , 5 , 4 , 3 , 2 , 1 分,也就是说,是存在可能性在测试结束之后分数仍然为 0 的。

比赛过程中,我花了许多时间来分析这个奇怪的评分标准。

对于这种评分标准,常见的策略有两大类: (1) 所有数据的成绩比较平均 (2) 在一种数据风格中特别突出呢。

根据数据描述, 50 个数据可以分为 10 种不同的风格。参加比赛的总共有 50 名选手,如果所有分数是完全平均分配的话,分数是 31 分,这个数字意义不大。但如果设想分数的 80% 会分配在前 10 名中(根据当时选手的水平,这个假设还是比较合理的),这样前 10 名的平均分数是 124 分左右,也就是说如果想挤进前 4 ,至少也要 100 分以上,如果想争取冠军估计需要 200 分左右。如果选择一种策略,使得它只在一种数据风格中特别突出,分数只有可怜的
50 分,而且很可能有许多有同样想法的选手,所以 (2) 不太可取。

在决定选择比较平衡的策略 (1) 的之后,需要再考虑一个问题,如果最终目标是 150 分,那么平均分数只需要 3 分,也就是说每个数据可以允许有 5 名选手超过自己。这些必要的分析帮助我明确了努力的方向,面对这种开放性的题目,多分析题目的特点往往可以达到事半功倍的效果。

还有一个重要环节是调整估价函数,机器学习其实是一个很好的策略,可惜我当时不会。其实当时我做的事情,本质上就是人工模拟机器学习,手工调整了 1 个半小时,眼都花了。而且我犯下了一个致命的错误,记得记分方法非常鼓励一次消去多行,也就是说对于平坦的数据,一次消去 1-3 行的权值应该可能设置为负数,而我只把他们设成了 0 ,使得程序对于平坦的数据分数不高。 Macsy 就考虑到了这一点,只可惜一个很奇怪的技术问题(在 Linux 和 Windows 下的 CLOCKS_PER_SEC
参数是不一样大的,想使用卡时策略时千万不能事先把这个数字取出来设置成 CONST )使得 Macsy 没能成功。

由于 Macsy 和 LemonTree (同样的技术问题)的出局,我在许多数据中得到了很高的分数,最后的总分达到了是 255 ,领先了第 2 名有 99 分之多。其实现场的许多选手的程序风格相差并不大,可能我唯一多做的事情就是建立了一个博弈树,多搜索两层,这样比直接贪心的程序看得更远一些。后来事实也证明了,排名靠前的选手大多都是比较平衡的策

抱歉!评论已关闭.