竞赛的代码不需要可读性,可维护性,可拓展性。可以称之为:一次性代码。题目通过了就OK,哪怕过后自己都看不懂(夸张了)也无所谓。而做产品却需要考虑代码可读,可维护,可拓展等等。完全相反。
用算法书做例子没任何意义。书是给人读的,可读性是第一位的,必要时候还采用伪代码,甚至流程图。代码却不是。一般不需要别人读懂,最多最多,另外俩队友能懂就可以了。
另外,到底是什么给你错觉,竞赛的人都没有读过《设计模式》《重构》《代码大全》《代码整洁之道》这类的书???做项目的时候,人家写得代码未必比你差哦。
另外,题主贴的代码,完全不觉得混乱。至少没有预定义一堆宏。不少竞赛选手,for循环之类的都是宏定义。
举例子:(十年前竞赛中个人常用的,不完全)
a,b,基本就是普通变量了
c,可能是count,或者写cnt;也可能是单个字符char
d,可能是距离distance,几何,图论常见
e,可能是误差error,或eps;也可能是边 edge,图论常见
f,可能就是函数function,dp的状态转移方程直接写过来的。也可能是流flow,例如在最大流算法的时候
g,也常用作函数,数学的习惯。也可能是图graph
h,也可能是函数,也可能是高height,也可能表示heuristic,也可能是高位置的high
i,j,k常见循环变量,下标
l,可能是长度length,可能是左侧left 或者低位置low,也有时就做普通循环变量。(ll是long long,用cpp答题的可是常见了)
m,一般是个数之类的,也可能是中间middle;也可能是模modulo
n,一般表示个数,可能是是node节点;也可能是下一个next,也写nxt
o,不常见,尤其容易和0混了。O可能表示圆心。oo表示无穷,象形数学的无穷大符号。
p,一般用作指针pointer。偶尔也特指父节点parent。
q,从p过来的,需要第二个指针时,一般考虑q
r,半径radius,余数remainder,右侧的right
s,字符串string,也写st,str;也表示源点source,也写src
t,临时变量temporary,也写tmp,temp;也表示汇点/目的地,也写dst;也表示树tree(树也常用rt,即根root表示)
u,v,图论的点或边,图论书就常用这俩字母
w,权重weight;宽度width
x,y,z,基本和数学一样,表示坐标,几何题常见。非几何的话,不多见
多字母的就更多了,有很多都是缩写,而且是学术界公认的缩写。
比如rmq,lca这些,不学算法的话,你知道啥意思?
看到st,能想到sparse table,想到倍增算法?
看到dc3,能想到difference cover, size 3,想到计算sa(suffix array,后缀数组)?
看到fft,能想到快速傅里叶变换?
看到kd,能想到是k-dimensional tree?
没见过的东西多了,自然就看不懂代码了。计算几何,组合数学,初等数论,数据结构和算法(包含了图论)等等多看看书,就能看懂绝大部分竞赛的代码了。
你说的a、b、T、s、t、f、dp什么的,其实在OIer中(至少是写这道题的OIer脑子里)都有固定的含义。(如果没有,那么这个含义可能在问题描述中给出)而不是乱写的。
就像物理学中,每种物理量都用一个字母表示,而不是一串中文或英文表示,这是很自然的。
这也不是什么OIer独有的,“文学”伪代码(如算法导论)中多的是这样的单字母变量。
不过这两者和OIer的区别在于人家可以用符号,OIer只能用n,算导可以写|V|,物理学的导数、积分等各种符号更是给人一种高级感。
代码规范和看不看得懂程序也没多大关系,你要没听懂算法,直接看复杂点的算法代码就是找死,不管那个算法用了多nb的“规范”把注释“蕴含”在变量名中。
你可以去著名的“工程代码”如gnu c++ pb_ds或者Java标准库源码或者认真以让人看懂为目的编写的“文学代码”如算法导论,看看那些算法实现(不看注释、代码外的注解),看看能不能看懂。
至于OIer的注解,正如上述“工程代码”“文学代码”一样,是写在整体代码之外的,OIer一般用md等方式编写于整个代码之上这和“工程代码”编写一大坨注释于整个代码之上与“文学代码”编写一大坨注解于代码之下没什么区别。
当然OIer的题解中往往又有一小部分约定俗成的内容,需要递归学习,这和论文里的引用、”工程代码“上的注释里贴个网址是一样的。(当然,OI比赛场上这些都是不可能去写的)
只有写OIer称之为“模拟题”,研究者称之为“trivial”,或者工程上称之为“业务逻辑”的代码时,才能得到“好的”代码不需要注释就能看懂的结论,在此基础上由于存在“让人看懂”的需求,才会产生“代码简洁之道”这样的要求来满足看懂的同时尽量减少工作量(交接或写注释的工作量)。
而OIer的代码,虽然部分仍属于研究者的“trivial”范围内,但已不适用“好的”代码无需注释就能让人看懂,同时让人看懂的需求也不甚强烈,为此编写出来的尽量减少工作量的代码就如你所示。
PS:你应该庆幸没有看到以让人看不懂、编译器能识别的最短代码等为目的而编写的代码。
没想到来回答的全是学竞赛的
顺手刷到这里,看到这句直接喷了出来。
这个问题竞赛生不回答谁回答?题主的目的是吸引广大程序员喷OI吗?
现身说法:鄙人搞竞赛的时候不敢说前1%,至少也是代码风格比较好看的前10%吧,我是会尽力把代码弄得易读的,因为我有一种搜集个人历史代码的爱好。。。
先说lz贴的这份代码,之所以觉得不好看,其实主要问题不是别的,只是“ll”满天飞,“ll”满天飞的唯一原因,是每次都敲“long long”这九个字母太费劲了,为什么要把这么多变量设置成long long呢(其实我认为这份代码的作者大意了,如果让我来,我会把循环变量i也设成long long,整个代码里找不到一个int)?因为否则的话,没准忘了哪个地方,就数值overflow了,你跺你也麻,真的。。。
至于inline和void缩成il和vd,这个属实意义不大,应该属于一种强迫症行为吧。。。
这里我要指出一点:考场机器上只有devc++或者codeblocks这样的IDE,自动补全不好用,这个是很重要的。。。
其实我倒是觉得,如果考场能用Visual Studio那种级别的,敲两个字母按tab自动补全,打错也能模糊联想的编程环境,那没准大家确实会把命名弄得漂亮些,毕竟除了少数签到题/工程题,对手速的要求也真没那么夸张,尤其是一共就三道题的中学竞赛,毕竟智力输出速度是个瓶颈。。。
在没有自动补全的情况下,难道要手敲包含>=1个完整单词的变量命名,一个字都不能错?你当我傻啊。。。
但是,以我的经验,变量名弄讲究点,也就仅此而已了,剩下的什么运算符打空格,多换行云云还是不太现实的,这里有三个因素:
1、竞赛代码极度依赖肉眼排查算法错误,这要求所有重要代码块都能“一眼看过去”,因此行之间必须安排得比较紧密(比如我一直是大括号不换行党),甚至不能打空格,以缩小显示宽度(你知道中学集中采购的显示器分辨率有多完蛋吗),这就是lz贴的代码里面,setN()函数里面那个while写成一行的原因。。。
2、竞赛代码只有一个.cpp,不能运用工程中常用的声明-定义分离方法。况且其中大部分都是逻辑模块,而不像实际工程中有大量的代码接口,这导致没有必要像工程中那样,用接口本身的定义方式去展示接口的内容,因为实际的内容都在里面。。。
3、背模板(像最短路这样的经典算法模块)这事讲究一个练死劲(雾),也就是讲究一个完全的精确性,在背的时候,甚至会不自觉地精确到“哪个变量声明在什么地方”这样的程度。这就要求不开代码补全以避免干扰(不同环境中不同的代码补全配置可能会影响背默模板的手感,这是很致命的),也要求把这些模板弄得非常精简,以便于记忆和对比记忆检查。这可以解释为什么lz贴的代码里面,pow()函数的那个while循环搞得这么紧密,更新x和更新y也要写在同一行(我猜测这是作者的记忆方式,更新x和y在一起,否则容易漏一个)。举个例子,floyd算法的三个循环变量,竞赛界的祖宗之法是分别叫做k、i、j,你换成什么middleNode,startNode,endNode试试,是不是记忆难度一下子就上去了。。。
说个趣事,敝中学的竞赛教练大概是得知了“windows下用MinGW编译”,然后按照自己的理解,在教学中把我校统一的编程工具定成了MinGW IDE,惊不惊喜,意不意外。。。
这玩意没有任何能用的自动补全功能,导致我在相当长一个时期内,哪怕换成devc++,也要手动关掉自动括号补全,每写一个函数都敲一次shift+{再敲一次shift+},无他,肌肉记忆习惯了。。。