tricks - 写代码

实现技巧

负下标

有的时候我们需要存一些负的东西,比如我就只要一个 \(-1\),或者说值域是 \([-10^6,10^6]\),而我懒得写平移

那咋整呢?

取下标:a[i],它的本质是 *(a+i)

开一个pool,然后开一个指针a指向pool+1,就可以访问 a[-1] 了

关于二维数组:

二维数组事实上是把每一行串起来变成一维数组存储的

所以说,比如你开了一个 int a[10][10],访问 a[2][-1] 等价于 a[1][9]

然后我们只需要开一个 p,指向二维数组的一行;然后就可以访问 p[-1][-1] 这样的下标了

那么二维数组的“一行”,是什么类型呢?实际上,是 int (*p)[10] 的类型。也许你在传函数参数的时候就注意到了,传数组的时候,第一维可以不限,但是后面必须要限制。这里也是一样的。

int (*p)[10] 只是一个特例,你可以把 10 换成任意数,但是要保持和你开的第二维相同。另外,由于优先级问题,这里的括号不能省略。

以下是一个例子,使得你可以访问 a[-100~100][-100~100]

int pool[202][202];
int (*a)[200]=pool[101];

为什么要多开两个呢?注意到 a[100-100][-1] 是不被允许的,所以 a 必须指向第 101 行,此处要躲开一个;后面要访问到 a[101+100=201],而此时我们才开了 201 行,那肯定不行,所以要开到 202

在不造成太多浪费的情况下,多开一两个保安全还是不错的。

动态开点

固然可以写一个映射函数,如

#define id(x,y) (x-1)*m+y

事实上,为了好看,与节省空间,可以这样写

int id[N][N],tot=0;
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) id[i][j]=++tot;

也就是每次用 ++tot 来开点。这样更加的灵活,易懂(因为数组有名字)(当然函数也有名字,但是括号和中括号混合在一起闲的很乱,全都是中括号则很整齐)

节省空间是因为,有的时候你为了使映射函数好写,可能会浪费一些区间。

具体的说,这种方法的优势体现在网格图中有障碍,或者是有很多个东西要一块编号的时候,或者是维度高的时候,或者是这个情况结合同时出现(有很多的高维度有障碍的东西要一块编号),等等情况。

当然映射函数也有它的好处,比如id数组开不下的时候映射函数就可以派上用场了。当然,为了可读性,也可以用map,如果必要。

花括号

不管是否换行与否,尽量不要省略花括号,如:

if (...) do_something

应该写作

if (...)
{
	do_something;
}

尽管只是很简单的一句话。因为这样不费很多力气,而且方便后期加入功能,与调试。

而且看起来很整齐,强迫症狂喜

多封装点东西

一段比较长而且比较simple的东西不断重复,封装一个函数出来,比如处理字符串题是要用的鸡数排序

这使得代码的逻辑清楚,不让大量的杂乱的东西影响阅读

也可以分一些class/namespace,把每一段功能区分开来。比如一个题目需要先预处理一些(复杂的)东西,然后跑一个SAM,然后还要一个线段树合并,然后还要blablabla,就可以把预处理开一个namespace,然后SAM写一个class,合并的线段树写一个class,一块一块的非常清楚。

先放一些INF

比如我们要二分,或者要删东西,删到空或者是二分到两边,需要一些特殊处理,有点小麻烦。此时我们可以先放几个INF进去,减少代码难度。

比如LCT实现MST的时候,就可以先放一堆INF边

这个trick是 马老师博客 里看到的!

image-20210723144213171.png

调代码技巧

与理论有偏差, 可能是其它地方的问题

有些时候我们在草稿纸上,或者脑子里,想到的一个式子,写出来不一定对,但是稍微调一下就对了。

这个时候不一定是式子错了,而多数情况是别的地方写错,阴差阳错的对了。

比如我们推出来某一块地方 \(i\) 应该是 \(0...n-1\),但是换成 \(1...n\) 莫名其妙的对,而原来那个不对。

我一看,嗷,原来是我数组没清空

拍!

平常自主练习的时候也不要吝啬时间不对拍。平常多试试对拍,考场上也会熟练一些,并且也是一种能很快调出来bug的方法。

像我以前通常是找一篇相同做法的题解对着代码调,一看就是半天,jb也看不出来。

posted @ 2020-12-27 19:18  Flandre-Zhu  阅读(91)  评论(0编辑  收藏  举报