NOI特别夏令营的总结和一些经验分享
This post is written in Chinese. Please consider using Google Translate
虽然很贵,我还是花了3800元来参加“NOI2009特别夏令营”。经过这一星期的培训,我有不少感触。7天做了5套题,其中三套是绍兴一中的同学出的,一套是吕潇、陈键飞和任春旭出的,还有一套是CEOI。期间绍兴一中的神牛有三天的难题讨论。不得不承认,这些难题大部分对我来说太难了,以至于无法接受。最大的收获还是考试的经验,包括心态的调整,对难题的应付手段,以及考试选题的策略。这些经验是极端重要的,对于NOI来说,甚至比硬实力更加重要。我觉得一场考试这么几件事要做:看题,选题,分析,编码,调试,测试,骗分。
1、看题
拿到试卷以后的第一件事就是看题。看题不是看小说,要仔细阅读。当然,阅读也不宜过慢,刻意制造紧张的气氛会极大地影响发挥。NOI的题目没有赤裸裸的,都是精心包装过的,阅读就是解开这个包装的过程。首先从题目名看起,认真阅读问题背景,要明白题目在表达什么意思。一边阅读,一边在脑中建立一个简单初步的模型。读完题后立刻看数据格式,然后阅读样例,如果是图论题,在纸上把样例中描述的图画出来,手算一下样例,这样有助于加深理解题意。这时候不要着急开始想题,把题再读一遍,第二遍看题可以适当加快,但不要放过细节。
2、选题
除非你是神牛级人物,否则像NOI这样的比赛你是不可能把题全部想出来的,那么选题就是十分重要的。把所有题先全部看完,对每道题进行简要的思考,每个题不超过30分钟,千万不要陷进去,有思路后即可停止。当你有了全局性的认识以后,这时再开始选题。
选题意味着要决定深入思考哪个题,做哪个题,不做哪个题,先做哪个题,后做哪个题。深入思考,当然是是要思考已经有大致思路的题,在考场上想了半个钟头还没思路的题,再想下去也很难想出来,即便是想出来了,浪费大量时间也是得不偿失的。我认为要先做把握大的题,什么是把握大的题?也就是思路完整,算法熟悉,易于调试的题。这样的题是拿分的关键,也是和别人拉开档次的法宝,一般来说一场考试中能有一道就是大幸了。更多的时候,可能会发现没有把握很大的题,这时不要着急,静下心来选择一道有思路的题深入思考。
如果确定用随机类算法解决提交答案题,可以先考虑开始写,这样在剩下的几个小时中都可以用来运行随机程序,以获得更好的解。
3、分析
有了思路,要开始具体分析一个题的算法。分析前首先要确定一个目标,我是要把这道题AC掉呢,还是拿部分得分足矣?这要看题目的数据规模,把脑中的思路简要抽象成算法,分析算法的时间复杂度,确定目标,着手优化算法。最后,确定算法的每个细节,思考各种极端的和边界的条件,把完整的想法转化为完整的算法,在纸上写出算法的流程,准备编码。
有时候,并不一定有拿满分的思路的题就要写完美的算法,尤其是复杂的数据结构题!这些题经常是一个陷阱,令选手陷入其中不能自拔。浩浩荡荡写完二三百行程序,欲调试出而不能,而朴素的算法花费极少的时间拿到可观的分数,孰轻孰重还要根据自己情况来衡量。
4、编码
在外行人看来,仿佛变成编程序就是信息学竞赛的全部,其实这是很小一部分,但却是很关键的一部分。编码细心与否,直接决定了下一步也就是调试的难度。看着自己在纸上写出的流程图,小心地把代码写出来。这一步不求快,但求稳,一定不要犯低级错误。建议写完每个模块后立即检查每条语句与所想的是否符合,写完整个程序后不要急于编译,先把程序通读一遍,确认无误后再开始编译调试。
5、调试
首先用样例(或自己构造的小数据)测试一边程序,如果得出了期望的结果,可以继续测试。否则一般会遇到这几种情况,崩溃、超时、结果错误。崩溃问题一般是这几种情况的后果:数组下标越界,访问无效指针,栈溢出。如果算法可行,那么超时很可能是由无穷递归或死循环造成的。可以使用输出语句或调试器跟踪到程序错误的位置,然后检查有没有语句逻辑错误。如果实在难以发现,可以使用输出语句,或调试器单步进入进行跟踪,发现异常的部分及时修改。找到结果错误原因,尽量不要一开始就是用单步调试,不妨把建模过程和程序每步的结果都输出,这样比单步更容易发现错误。
6、测试
调试和测试是交叉进行的,稳定测试无误后,开始规模测试。首先要构造边界、极端数据,因为这些是较容易忽略的死角。接下来,可以考虑随机数据对拍测试(非完美算法可以简化此步)。首先写一个朴素程序,要保证正确性,不求运行高效率,然后写一个数据生成器。接下来生成数据,两个程序分别运行,对比结果,反复大量测试(对拍)。发现错误可以及时调试并修正,测试可以直到放心为止。充分利用时间,还可以在思考下一道题的时候一边进行着测试。
7、骗分
会的已经写完的,不会的写了非完美算法,你还有剩下的工作要做。充分利用NOI的规则,并不要求算法的完美性,只要能拿到分就是好方法——这一步被人称为骗分。
骗分,不是胡乱交一个程序,输出0,IMPOSSIPLE甚至一个随机数之类,而是有计划,有预谋地搞。骗分的原则就是,尽量避免超时。为什么?超时就是0分,如果不超时地输出一个结果,还有可能拿到分。一般来说,对于非完美算法的较大数据,较好的方法是贪心、卡时和随机。
贪心法需要大胆的猜想,即使是有反例的错误猜想,稍作修改,即可用于应付大数据。毕竟出题人也未必能考虑的你想到的贪心算法,况且范例也是不容易构造的,毕竟随机数据在NOI的测试数据中有相当的比重,贪心是很划算的。对于需要反复迭代求最优值的算法(例如搜索),不妨采用卡时的方法。因为可能有这种情况,我的程序要运行5秒,但实际上有可能在第1秒以内求出的最优解已经是全局最优解,剩下的4秒就是无用的。虽然读取系统时间time和clock函数都是禁止直接使用的,我们大可不必真正卡“时”,只需限定一个迭代次数即可。随机算法有点撞大运的感觉,其实不是这样的,较好的随机算法(例如模拟退火,遗传算法)得到全局最优解的概率相当可观。
8、意外
有时候在考场上会遇到意外,例如写了半天,发现算法根本是错误的;调了好长时间都调不出正确结果,犹豫是否放弃;精神过于紧张,以至于体力不支;操作系统或环境出现意外错误等等。这些因素都会很大程度上影响发挥,但当我们真正要面对这些问题的时候,该怎么办?这是一个值得讨论的话题,我很难给出一个合适解决方法,但这时候发挥最重要作用的是心态,良好的心态才是制胜的最根本条件。
为期一周的特别夏令营结束了,明天我将从杭州上车返回郑州,中途可以去西湖玩一玩,保持健康良好的心态来迎接NOI。我的目标是在NOI上发挥出我真正的水平,不求金牌,因为我清楚我的实力离金牌还有相当的差距,愿所有看到这篇文章的同学们能够有所帮助,在将要到来的NOI2009和年底的NOIP2009中取得优异的成绩。
后话:等我正式退役以后,我会写更多的内容,将我的一切经验毫无保留地献给所有在信息学竞赛路上继续奋斗的同学们。