做题细节

来源:互联网 发布:真田太平记 知乎 编辑:程序博客网 时间:2024/05/03 18:09
1. 如果题目是枚举的话,即最后化成十分简单的形式比较小,
可以直接将各种不同状态的结果运算过程写出来,但是这并不见得比写函数要快多少
而且比较容易出错,比如下标没有更改之类,这种错误比较烦人,因为你会查看算法,
但是算法本身并没有错误,所以如果复制粘贴的话,注意不同情况的不同点,如果自己
不够细心,最好写成函数的形式(注意判断边界),以防出错.//16:35 2004-4-17
2. 在编程之后检查的第一件事就是初始化,
你的初始化也许写在循环体之外,故只能AC一组测试数据,sample.
//19:16 2004-5-6
3. 写多重循环时得注意i,j的顺序问题,有的时候会把该写j的地方写成i。
这个毛病很容易犯,而且难于debug
//17:29 2004-5-6
4. scanf("%d %d..\n",....), 格式中的,可以一直读到回车符,
但是读到回车并不会停止(gets()读到回车就不读了),
而会一直将回车后面的空格读进去.
#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
freopen("d:\\test.in","r",stdin);
int a;
char c;
scanf("%d\n",&a);
c=getchar();
printf("%d\n",c);
return 0;
}
//test.in
10
X
X
try this
//17:29 2004-5-14
5. abs 函数的定义是不支持long long 和 __int64 的,
所以在涉及到高精度的时候, 最好用if else判断或者define,
但是速度较慢 #define abs(a) (a>0?a:-a)
//13:07 2004-5-25
6. 使用pow(double x,double y) 函数的时候要注意,
当 x<0 && y>0 那么输出并不是预期的结果,而是-1.#IND00
try this:
printf("%lf\n",pow(-1.5,0.23));
以当不知道 x 的范围,最好fabs() 一下.
//11:09 2004-5-26
7. 一般的judge都装在linux上,可是linux对 atoi() 不支持,但是没有关系,
因为无论win抑或linux都对 atol() 支持。如果在sscanf()没有必要的情况
下,推荐用这个函数,因为sscanf这个函数的效率很比较低,如果比赛的时候
算法不好的话,哼哼...
//11:11 2004-5-26
8. 程序中涉及到输出浮点数的时候要注意0.00,在gcc下(win下也因该是)
一个很小的负数会输出为-0.00,不会输出0.00,而测试数据的输出可能是0.00,
也可能是-0.00(这就要看题目中精确解是否为0,如果果为0,那么这结果
输出0.00是合情合理的),因此,如果平时做题被郁了,这里也是应该考虑的地方,
(平时做题的时候要注意,比赛的时候是人工评判,应该不会计较)
//20:36 2004-7-17
9. 没想到啊,以前只注意到gets和scanf("%s")在输入文件处理上的区别
但是同样的算法的工大的1050却看出了gets实际上比scanf("%s")快很多,
差不多是3倍时间,在比赛的时候如果遇到输入很多且比较简单的题目,
这个地方应该注意一下,但是首先还是要学会如何把这两个函数用好。
推论:getchar比scanf("%c")快,(我没有测试).
10. 有的时候可能会遇到大数的精度处理,这个是比较麻烦的事情,
虽然题目中会明确说明最后输出的结果可以用整数表示得下,
但这并不意味着中间处理的时候不会溢出,所以要防止中间步骤溢出的情况
long long 和 double 是比较合理的选择,当然有那么一种题目用double过不去
需要用 long double,long double 比 double的精度和表示范围均有所改进,
但是使用了浮点数类型就意味着要进行精度处理,如果知道最后的答案应该是整数,
那么可以在最后得到的浮点结果基础上加一个比较小的数
比如说,精确解是5,但由于误差得到的结果为4.99999,这样取整后的结果为4,
显然WA,这时,加上一个小一点的数就可以处理这种情况。
这些编程细节平时一定要注意,要不然到比赛的时候会很郁闷的,
往往是多花1个小时调这个微不足道的bug,调不调的出还不一定。
11. scanf(" ")的作用是读清当前位置开始的所有空格,制表符,回车符。
譬如:输入文件为:
( <>之中的内容,\之后的为转移字符 )
start of file:
<100 \t\n\t\t\t nan>
end of file
那么,如果在 scanf("%d",&i); 之后,加上一个 scanf(" ");
然后再用 scanf("%c",&c); 读取字符的话,读入的是 'n'
PS: scanf("\t"); 和 scanf("\n"); 都有这个功能!
//2004-8-3 15:18
12. 在移位运算的时候系统好像先用数据移位数对位宽取模,然后再进行移位运算
因此不要以为 1<<32 == 1<<0 其实这个结果是 1
13. 如果在一个递归程序的每一层都申请接近80字节的栈资源,
那么递归深度VC大约80*80层,gcc 大约120*120层。
80 * 80 *80 = 512,000 80 * 120 * 120 = 1,152,000
在写搜索或递归程序的时候要注意一下。
注意:函数调用本身就要占一定的栈空间。
14. 动态规划初始化需特别注意,尤其当需要多信息构造最优解时,
每个信息都要初始化,多花一点时间检查。
15. stl中stable_sort比较耗内存,时间复杂度为O(n*lg(n)*lg(n))
最好情况下为为O(n*lg(n))
16. 可以将debug需要做的操作写成函数,提交时不调用就成了。
或者用宏 ifdef DEBUG ...
17. 为bool变量赋值时即使赋为2,那么bool变量的内存空间的值仍旧为1
bool变量作自加操作,那么如果原来为true,自加后的结果仍旧为true
jackie pointed out:
不要判定一个bool变量是否等于true,而是直接写 if(bool_var)
//14:55 2004-9-15
18. printf("%.2lf\n",0.); != printf("%.2lf\n",0);
在编译的时候编译器会将0.解释成double型,而将0解释成整形
有的编译器在类型的转换上作的不是很好, 会出现意想不到的毛病
#include<stdio.h>
int main()
{
printf("%.2lf\n",0);
printf("%d\n",0.);
return 0;
}
在G++ 下编译并运行这个程序,得到的结果是
-2.00
0
所以平时在写程序的时候该写成0.的地方就写成0.,
防止这种死都不知道怎么死的漏洞发生
printf在运作的时候先将0.这个double push 进栈,然后按照格式打印
19. vector是C++中的一个类,它支持动态的数组增长,也就是说它的长度
随着输入数据的增加而动态开辟,这给我们编程带来了很大的便利,但
在使用之前我们要知道它的原理。如不特殊指定,vector初始化时是0
元素长的,当向它push一个元素时,如果原来空间足够,就直接压入,
如果不够,就将原来空间乘以2,然后再将元素压入。这个空间乘以2的
过程是先开辟一个2倍的空间,然后将原有的数据拷贝过去,因此在进行
插入的时候vector的内存空间是变化的。问题就出在这里,平时我们编程
的时候有的人喜欢用iterator,因为这个东西比较快,而且比较灵活,但
是当你写出如下代码的时候
vector<int> arr;
vector<int>::iterator it;
for(it=arr.begin();it!=arr.end();++it)
{
//do sth.
arr.push_back(some integer);
//do sth.
}
在上面的操作中,我们由于某种原因向vector<int> arr中压入了元素,那
么这个压入的元素很可能引起arr内存空间的重新分配,但是iterator it
并不知道,它的操作就是自加,这样it实际上所引用的内存空间并不是arr
中的元素,也就是错误所在了。但这并不能说iterator不好,只是说用一
个东西之前应该先弄清楚机理,以防不测。
这个毛病在BFS的时候容易犯。
20. 在平时写程序的时候,我们要经常和变量的值打交道,这就不可避免的遇
到 == ,相信每个学过 C 语言的人都知道和 == 和 = 的区别(考点嘛)
但是实际的变成中还经常犯这个错误,一种比较推荐的方法是将判等时候
的常量写在前面,变量写在后面,这是因为 if(x==1) 与 if(x=1)
都可以通过编译但是 if(1=x) 却不可以通过编译。
//2005-1-20 18:44
21. 一个问题从一个角度上看会有很多种可能性,如果想要正确得解这道题目,
就需要对各种可能性进行处理;但是如果换个角度(也许需要某些转化),
可能性就只变成几种了,这样处理起来也就方便些。找出各种可能的共性,
按照这个共性进行分类也许可以得到好的求解方法。
//2005-3-14 13:55
22. 在利用数组作先广搜索的时候,可以利用数组中的位置来索引某个节点的
相关属性,也可以用元素的ID来索引他的相关属性,两种方法没有本质的
不同,但是有的时候可能将两种方法混用,这样就错了,在debug的时候
应该检查一下是否有类似的毛病。
//2005-3-16 10:32
23. 猜想并证明是一种很有用的定理证明方法。
//2005-4-11 9:20
24. 个人认为:归纳法是不能被其他证明方法所替代的,而反证法为我们提供
了思考问题的正确方向。
//2005-4-11 9:21
25. 是用algorithm中的sort函数的时候,有的时候要自己写比较函数,比较
函数的要求是当第一个元素小于第二个元素的时候返回true,其他时候返
回false,一定要注意千万不要在等于的时候返回true。系统用的是快速排
序算法,拿数组中的一个元素作为中间值,这个值在数组中是必然存在的,
且它会在快排的时候始终处于未处理数列的中间,因此系统也就没有边界
检查,程序从两侧开始循环,当比较函数返回false的时候停止,并交换,
也就是说数组中的那个开始选定的中间值作为哨兵元素,如果等于的时候
返回true,这个指针便会一直处理下去并很可能溢出。
//2005-4-26 20:35
26. 存在这么一些恶题,比如说图上的一道简单的拓扑排序,题目中会给出
function dependency, 但是一个函数除了会依赖别的函数以外还会依赖
自己(递归嘛),这还不算,他还会多次给出依赖某一个函数(也就是题
目中没有说明依赖的函数各不相同),最好设计一种比较健壮的代码,否
则如果被阴了,则极度郁闷。
这种E题在图上常见
//2005-12-15 16:20
27. main()函数也可以递归得调用自身,可以利用这个特点完成一些递归程序
//2005-12-16 10:00
28. STL中大多数的连续元素首尾地址作为参数都是左闭右开的,即右面的地址
实际上指向一个并不存在的元素。这一点在使用push_heap函数的时候要格
外注意,因为要压入栈的元素在右地址的左面那个单元。此外,STL中的堆
需要的比较函数是小于号,这样建造的堆是大根堆,如果传入的是大于号则
建立的是小根堆,切忌不要传入小于的非(即大于等于)或大于的非(即小
于等于),否则可能会出错(根据内部实现,sort函数肯定会出错)。
//2006-3-22 22:08
29. 当数组作为参数传递的时候只传递指针,并不会传递数组的值,且以下三种
情况等价:
(1) void cc(int *arr);
(2) void cc(int arr[]);
(3) void cc(int arr[10]);
如果用sizeof来计算arr的大小,得到的结果都是4,这是因为arr在这些函数
里就是一个指针,但是如果声明一个引用,则必须在函数中指定所引用的数
组的详细信息。
void cc(int (&arr)[10]);
这条声明语句实际上声明了一个10个item的int类型数组的引用,当用sizeof
来计算它的大小时,得到的结果是40。
//2006-4-2 12:43
30. 可变参数的用法,在c语言中,可变参数是通过在函数参数表的结尾用...来
表示后续出现的若干参数,例如:
int cc(char *format,...);
要注意在C语言中必须有一个确定的参数,而在C++中
int cc(...);
也可以通过编译。
为了使用这些不知道类型和数目的参数,可以使用stdarg.h头文件中定义的
va_start, va_arg, va_end宏来对他们进行操作,可以到网上找一找相关代码
//2006-4-2 16:15
31. 可变参数宏的用法,c语言的宏也可以定义成可变参数的,例如:
#define debug(x...) printf(##x)
x...就代表了可变参数,用##x就可以对这些参数进行解析,当然用y来代替x
亦可
//2006-4-2 16:27
32. typedef这个东西满有用的,尤其是涉及到复杂的函数类型定义的时候,比如说
我们要声明一个函数,这个函数只是带一个int作为形参,返回一个函数指针,
返回的这个函数指针为带两个参数返回值为整数,则我们需要这样定义:
int (*ff(int))(int *, int);
这已经很让人眼花缭乱了,如果我们定义的函数返回的函数指针所指向的函数类
型为返回值为整数指针,或者我们不定义函数,而是定义函数指针,那么就
很容易出错了,其实我们可以这样:
typedef int (*PF) (int *,int);
这样我们就新定义了一个PF类型,这个类型就是一个函数指针,
所以刚才声明的函数就可以写成:
PF ff(int);
这样就很难出错了:P
//2006-4-2 20:18
33. typedef还有比较有意思的用法,比如说定义一个函数类型后,就可以用这个
函数类型来声明函数(不是定义),例如:
typedef void func(int);
这样func就表示带有一个整数参数且返回为void的函数类型,自然
func cc;
就声明了一个函数名为cc,带一个整数为参数,返回值为void的函数。
但是如下这样就编译不过了:
func cc{
}
编译器返回的错误为typedef cannot be used for function definition
//2006-4-3 16:27
34. 我们很多时候需要通过打印信息的方式来显示程序现在的状态,但是碰到输出
已经定位到文件的程序就不是很爽了,据我所知有两种方式来处理
1. 重新定向到标准输入和输出
freopen("CON","r",stdin);
freopen("CON","w",stdout);
2. 通过perror()或cerr把字符串输出到stderr上,而stderr是monitor,但是由
于perror打印的格式有一个error NO,因此可能并不能让我们满意。
3. 利用stderr。在C下终端也看成一个文件,而stdout,stdin,stderr都是文
件指针,所以我们可以直接通过对stderr这个文件指针处理来将我们需要打印
的东西打印到终端上。
fprintf(stderr,"%s","Good!\n");
//2006-4-5 15:50
35. fgetc这个函数通过文件指针读取一个字节的信息,它的返回值为一个整数,因
为读的是字节,因此返回值的范围是0-255,也就是说在文件中存的FF被解析成
255返回而不是解析成-1返回,而这个函数只有在读到文件末尾的时候才会返回
-1(EOF),也就是说无论对于文本文件还是对于2进制文件我们都可以用该函数
读。
//2006-4-6 14:18
36. 通过fwrite向文件中写数据时,数据在文件内存储方式和内存中一致,本着低位
在先高位在后的原则,而这个接口比较适合用人的逻辑来编程,但实际上就是从
文件中一个字节一个字节读出来写道内存中或者从内存中一个字节一个字节读出
来写到文件中。
//2006-4-6 20:22
37. 在实际应用中,很多时候需要改变一个文件的某一小部分,一种很笨的方法是边
读文件边写,将修改的地方修改,但是这样一来时间的开销就十分可怕,尤其是
文件比较大的时候,其实我们可以采取另外一种方式:
用fopen("","r+");打开文件"rb+"亦可
然后通过fseek找到需要修改的地方,然后直接向这个位置写入,写入后close,
注意fflush文件指针之后文件并不会变,不知其原因。
这种方法并不是官方的方法,也许会有bug,但经过我的测试尚可:)
//2006-4-6 21:02
38. 在C语言中,一个经典的问题就是如何区分数组的指针和指针数组这两个东东,
而在C++中只存在这数组的引用而没有引用数组,下面的程序将无法通过编译:
void proc(int &arr[])
{
//do sth.
}
而定义一个数组的引用必须加上它各维的大小
void proc(int (&arr)[10])
{
}
0 0