收藏 分享(赏)

算法竞赛-入门经典-作者刘汝佳.doc

上传人:dreamzhangning 文档编号:2769313 上传时间:2018-09-27 格式:DOC 页数:68 大小:670.50KB
下载 相关 举报
算法竞赛-入门经典-作者刘汝佳.doc_第1页
第1页 / 共68页
算法竞赛-入门经典-作者刘汝佳.doc_第2页
第2页 / 共68页
算法竞赛-入门经典-作者刘汝佳.doc_第3页
第3页 / 共68页
算法竞赛-入门经典-作者刘汝佳.doc_第4页
第4页 / 共68页
算法竞赛-入门经典-作者刘汝佳.doc_第5页
第5页 / 共68页
点击查看更多>>
资源描述

1、算法竞赛入门经典目录目录 110第 1 章 程序设计入门 1121.1 算术表达式 .1121.2 变量及其输入 .1141.3 顺序结构程序设计 .1171.4 分支结构程序设计 .1201.5 小结与习题 .1231.5.1 数据类型实验 1231.5.2 scanf 输入格式实验 1231.5.3 printf 语句输出实验 1231.5.4 测测你的实践能力 1231.5.5 小结 1241.5.6 上机练习 124第 2 章 循环结构程序设计 1102.1 for 循环 1102.2 循环结构程序设计 1132.3 文件操作 .1162.4 小结与习题 .1192.4.1 输出技巧

2、.1192.4.2 浮点数陷阱 .1202.4.3 64 位整数 1202.4.4 C+中的输入输出 .1202.4.5 小结 .1212.4.6 上机练习 122第 4 章 函数和递归 1104.1 数学函数 .1104.1.1 简单函数的编写 1104.1.2 使用结构体的函数 1114.1.3 应用举例 .1114.2 地址和指针 .1144.2.1 变量交换 1144.2.2 调用栈 1154.2.3 用指针实现变量交换 1174.2.4 初学者易犯的错误 .1184.3 递 归 .1194.3.1 递归定义 1194.3.2 递归函数 .120算法竞赛入门经典4.3.3 C 语言对递

3、归的支持 1204.3.4 段错误与栈溢出 1224.4 本章小结 .1234.4.1 小问题集锦 1234.4.2 小结 .124第 6 章 数据结构基础 16.1 栈和队列 .16.1.1 卡片游戏 16.1.2 铁轨 .26.2 链 表 .46.2.1 初步分析 46.2.2 链式结构 .66.2.3 对比测试 .76.2.4 随机数发生器 .86.3 二叉树 .96.3.1 小球下落 96.3.2 层次遍历 .106.3.3 二叉树重建 146.4 图 .156.4.2 走迷宫 .166.4.3 拓扑排序 .186.4.4 欧拉回路 .186.5 训练参考 19第 8 章 高效算法设计

4、 18.1 算法分析初步 .18.1.1 渐进时间复杂度 18.1.2 上界分析 .2第 1 章 程序设计入门 第 1 部分 语 言 篇第 1 章 程序设计入门学习目标熟悉 C 语言程序的编译和运行学会编程计算并输出常见的算术表达式的结果掌握整数和浮点数的含义和输出方法掌握数学函数的使用方法初步了解变量的含义掌握整数和浮点数变量的声明方法掌握整数和浮点数变量的读入方法掌握变量交换的三变量法理解算法竞赛中的程序三步曲:输入、计算、输出记住算法竞赛的目标及其对程序的要求计算机速度快,很适合做计算和逻辑判断工作。本章首先介绍顺序结构程序设计,其基本思路是:把需要计算机完成的工作分成若干个步骤,然后依

5、次让计算机执行。注意这里的“依次”二字步骤之间是有先后顺序的。这部分的重点在于计算。接下来介绍分支结构程序设计,用到了逻辑判断,根据不同情况执行不同语句。本章内容不复杂,但是不容忽视。注意:编程不是看会的,也不是听会的,而是练会的,所以应尽量在计算机旁阅读 本书,以便把书中的程序输入到计算机中进行调试,顺便再做做上机练习。千万不要图 快如果没有足够的时间用来实践,那么学得快,忘得也快。1.1 算术表达式计算机的“本职”工作是计算,因此下面先从算术运算入手,看看如何用计算机进行复杂的计算。程序 1-1 计算并输出 1+2 的值#includeint main()printf(“%dn“, 1+2

6、);return 0;这是一段简单的程序,用于计算 1+2 的值,并把结果输出到屏幕。如果你不知道如何编译并运行这段程序,可阅读附录或向指导教师求助。即使你不明白上述程序除了“1+2”之外的其他内容,仍然可以进行以下探索:试着把算法竞赛入门经典“1+2”改成其他东西,而不要去修改那些并不明白的代码它们看上去工作情况良好。下面让我们做 4 个实验:实验 1:修改程序 1-1,输出 3-4 的结果实验 2:修改程序 1-1,输出 56 的结果实验 3:修改程序 1-1,输出 84 的结果实验 4:修改程序 1-1,输出 85 的结果直接把“1+2”替换成“3+4 ”即可顺利解决实验 1,但读者很快

7、就会发现:无法在键盘上找到乘号和除号。解决方法是:用星号“*”代替乘号,而用正斜线“/”代替除号。这样,4 个实验都顺利完成了。等一下!实验 4 的输出结果居然是 1,而不是正确答案 1.6。这是怎么回事?计算机出问题了吗?计算机没有出问题,问题出在程序上:这段程序的实际含义并非和我们所想的一致。在 C 语言中,8/5 的确切含义是 8 除以 5 所得商值的整数部分。同样地,( -8)/5 的值是-1,不信可以自己试试。那么如果非要得到 85=1.6 的结果怎么办?下面是完整的程序。程序 1-2 计算并输出 8/5 的值,保留小数点后 1 位#includeint main()printf(“

8、%.1lfn“, 8.0/5.0);return 0;注意,百分号后面是个小数点,然后是数字 1,再然后是小写字母 l,最后是小写字母 f,千万不能打错,包括大小写在 C 语言中,大写和小写字母代表的含义是不同的。再来做 3 个实验:实验 5:把%.1lf 中的数字 1 改成 2,结果如何?能猜想出“1”的确切意思吗?如果把小数点和 1 都删除,%lf 的含义是什么?实验 6:字符串%.1lf 不变,把 8.0/5.0 改成原来的 8/5,结果如何?实验 7:字符串%.1lf 改成原来的%d,8.0/5.0 不变,结果如何?实验 5 并不难解决,但实验 6 和实验 7 的答案就很难简单解释了真

9、正原因涉及整数和浮点数编码,相信多数初学者对此都不感兴趣。原因并不重要,重要的是规范:根据规范做事情,则一切尽在掌握中。第 1 章 程序设计入门 提示 1-1:整数 值用%d 输出,实数用%lf 输出。这里的“整数值”指的是 1+2、8/5 这样“整数之间的运算”。只要运算符的两边都是整数,则运算结果也会是整数。正因为这样,8/5 的值才是 1,而不是 1.6。8.0 和 5.0 被看作是“实数”,或者说得更专业一点,叫“浮点数”。浮点数之间的运算结果是浮点数,因此 8.0/5.0=1.6 也是浮点数。注意,这里的运算符“/ ”其实是“多面手”,它既可以拿来做整数除法,又可以拿来做浮点数除法。

10、提示 1-2:整数 /整数=整数,浮点数/ 浮点数=浮点数。这条规则同样适用于加法、减法和乘法,不过没有除法这么容易出错毕竟整数乘以整数的结果本来就是整数。算术表达式可以和数学表达式一样复杂,例如:程序 1-3 复杂的表达式计算#include#includeint main()printf(“%.8lfn“, 1+2*sqrt(3)/(5-0.1);return 0;相信读者不难把它翻译成数学表达式: 。尽管如此,读者可能还是有一些疑惑:23150.5-0.1 的值是什么?“整数-浮点数”是整数还是浮点数?另外,多出来的 #include是做什么用的?第 1 个问题相信读者能够“猜到”结果:

11、整数-浮点数= 浮点数。但其实这个说法并不准确。确切的说法是:整数先“变”成浮点数,然后浮点数-浮点数 =浮点数。第 2 个问题的答案是:因为程序 1-3 中用到了数学函数 sqrt。数学函数 sqrt(x)的作用是计算x 的算术平方根(若不信,可输出 sqrt(9.0)的值试试)。一般来说,只要在程序中用到了数学函数,就需要在程序最开始的地方包含头文件 math.h,并在编译时连接数学库。如果你不知道如何编译并运行这段程序,可阅读附录或向指导教师求助。1.2 变量及其输入1.1 节的程序虽好,但有一个遗憾:计算的数据是事先确定的。为了计算 1+2 和 2+3,我们不得不编写两个程序。可不可以

12、让程序读取键盘输入,并根据输入内容计算结果呢?答案是肯定的。程序如下:算法竞赛入门经典程序 1-4 A+B 问题#includeint main()int a, b;scanf(“%d%d“, printf(“%dn“, a+b);return 0;该程序比 1.1 节的复杂了许多。简单地说,第一条语句“int a, b”声明了两个整型(即整数类型)变量 a 和 b,然后读取键盘输入,并放到 a 和 b 中。注意 a 和 b 前面的double r, h, s1, s2, s;scanf(“%lf%lf“, s1 = pi*r*r;s2 = 2*pi*r*h;s = s1*2.0 + s2;p

13、rintf(“Area = %.3lfn“, s)return 0;这是本书中第一个完整的“竞赛题目”,因为和正规比赛一样,题目中包含着输入输出格1 在学习编程时,“明知故犯”是有益的:起码你知道了错误时的现象。这样,当你真的不小心犯错时,可以通过现象猜测到可能的原因。第 1 章 程序设计入门 式规定,还有样例数据。大多数的算法竞赛包含如下一些相同的“游戏规则”。首先,选手程序的执行是自动完成的,没有人工干预。不要在用户输入之前打印提示信息(例如“Please input n:”),这不仅不会为程序赢得更高的“界面友好分”,反而会让程序丢掉大量的(甚至所有的)分数这些提示信息会被当作输出数据的

14、一部分。例如刚才的程序如果加上了“友好提示”,输出信息将变成:Please input n:Area = 274.889比标准答案多了整整一行!其次,不要让程序“按任意键退出”(例如调用 system(“pause”),或者加一个多余的getchar()),因为不会有人来“按任意键”的。不少早期的 C 语言教材会建议在程序的最后加这样一条语句来“观察输出结果”,但注意千万不要在算法竞赛中这样做。提示 1-4:在算法 竞赛中,输入前不要打印提示信息。 输出完毕后应立即终止程序,不要等待用户按键,因为输入输出过程都是自 动的,没有人工干 预。在一般情况下,你的程序不能直接读取键盘和控制屏幕:不要在

15、算法竞赛中使用 getch()、getche()、gotoxy() 、clrscr() (早期的教材中可能会介绍这些函数)。提示 1-5:在算法 竞赛中不要使用头文件 conio.h,包括 getch()、clrscr()等函数。最后,最容易忽略的是输出的格式:在很多情况下,输出格式是非常严格的多一个或者少一个字符都是不可以的!提示 1-6:在算法 竞赛中,每行输出均应以回车符结束,包括最后一行。除非特别说明,每行的行首不应有空格,但行末通常可以有多余空格。另外,输出的每两个数或者字符串之间应以单个空格隔开。总结一下,算法竞赛的程序应当只做 3 件事情:读入数据、计算结果、打印输出。不要打印提

16、示信息,不要在打印输出后“暂停程序”,更不要尝试画图、访问网络等与算法无关的任务。回到刚才的程序,它多了几个新东西。首先是“const double pi = 4.0 * atan(1.0);”。这里也声明了一个叫 pi 的“符号” ,但是 const 关键字表明它的值是不可以改变的pi 是一个真正的数学常数 1。1 有的读者可能会用 math.h 中定义的常量 M_PI,但其实这个常数不是 ANSI C 标准的。不信的话用 gcc-ansi 编译试试。算法竞赛入门经典提示 1-7:尽量用 const 关键字声明常数。接下来是 s1 = pi * r * r。这条语句应该如何理解呢? “s1

17、等于 pi*r*r”吗?并不是这样的。不信,你把它换成“pi * r * r = s1”试试,编译器会给出错误信息:invalid lvalue in assignment。如果这条语句真的是“二者相等”的意思,为何不允许反着写呢?事实上,这条语句的学术说法是赋值(assignment),它不是一个描述,而是一个动作。它的确切含义是:先把“等号”右边的值算出来,然后塞到左边的变量中。注意,变量是“喜 新厌 旧 ”的 , 即 新 的 值 将 覆 盖 原 来 的 值 , 一 旦 被 赋 了 新 的 值 , 变 量 中 原 来 的 值 就 丢 失 了 。提示 1-8:赋值 是个动作,先计算右边的值,

18、再 赋给左边的变量,覆盖它原来的值。最后是那个“Area = %.3lfn”,它的用法很容易被猜到:只有以% 开头的部分才会被后面的值替换掉,其他部分原样输出。提示 1-9:printf 的格式字符串中可以包含其他可打印符号,打印时原样输出。1.3 顺序结构程序设计例题 1-2 三位数反转输入一个三位数,分离出它的百位、十位和个位,反转后输出。样例输入:127样例输出:721【分析】首先将三位数读入变量 n,然后进行分离。百位等于 n/100(注意这里取的是商的整数部分),十位等于 n/10%10(这里的% 是取余数操作),个位等于 n%10。程序如下:程序 1-6 三位数反转(1)#incl

19、udeint main()int n;scanf(“%d“, printf(“%d%d%dn“, n%10, n/10%10, n/100);return 0;此题有一个没有说清楚的细节,即:如果个位是 0,反转后应该输出吗?例如输入是 520,输出是 025 还是 25?如果在算法竞赛中遇到这样的问题,可向监考人员询问 1。但是在这里,两种情况的处理方法都应学会。1 如果是网络竞赛,还可以向组织者发信,在论坛中提问或者拨打热线电话。第 1 章 程序设计入门 提示 1-10:算法竞赛的题目应当是严密的,各种情况下的输出均应有严格规定。如果在比赛中发现题目有漏洞,应向相关人员询问 ,而尽量不要自

20、己随意假定。上 面 的 程 序 输 出 025, 但 要 改 成 输 出 25 似 乎 会 比 较 麻 烦 我 们 必 须 判 断 n%10 是 不 是 0,但 目 前 还 没 有 学 到 “根 据 不 同 情 况 执 行 不 同 指 令 ”( 分 支 结 构 程 序 设 计 是 1.4 节 的 主 题 ) 。一个解决方法是在输出前把结果储存在变量 m 中。这样,直接用%d 格式输出 m,将输出25。要输出 025 也很容易,把输出格式变为%03d 即可。程序 1-7 三位数反转(2)#includeint main()int n, m;scanf(“%d“, m = (n%10)*100 +

21、 (n/10%10)*10 + (n/100);printf(“%03dn“, m);return 0;例题 1-3 交换变量输入两个整数 a 和 b,交换二者的值,然后输出。样例输入:824 16样例输出:16 824【分析】按照题目所说,先把输入存入变量 a 和 b,然后交换。如何交换两个变量呢?最经典的方法是三变量法:程序 1-8 变量交换(1)#includeint main()int a, b, t;scanf(“%d%d“, t = a;a = b;b = t;printf(“%d %dn“, a, b);return 0;可以将这种方法形象地比喻成将一瓶酱油和一瓶醋借助一个空瓶子

22、进行交换:先把酱油倒入空瓶,然后将醋倒进原来的酱油瓶中,最后把酱油从辅助的瓶子中倒入原来的醋瓶子里。这样的比喻虽然形象,但是初学者应当注意它和真正的变量交换的区别。借助一个空瓶子的目的是:避免把醋直接倒入酱油瓶子直接倒进去,二者混合以后,将很难分开。在 C 语言中,如果直接进行赋值 a=b,则原来 a 的值(酱油)将会被新值(醋)覆盖,而不是混合在一起。当酱油被倒入空瓶以后,原来的酱油瓶就变空了,这样才能装醋。但在 C 语言中,进行赋值 t = a 后,a 的值不变,它只是把值拷贝(即复制)给了变量 t 而已,自身并不会变化。尽 管a 的 值 马 上 就 会 被 改 写 , 但 是 从 原 理

23、 上 看 , t=a 的 过 程 和 “倒 酱 油 ”的 过 程 有 着 本 质 区 别 。提示 1-11:赋值 a = b 之后,变量 a 原来的值被覆盖,而 b 的值不变。算法竞赛入门经典另一个方法没有借助任何变量,但是较难理解:程序 1-9 变量交换(2)#includeint main()int a, b;scanf(“%d%d“, a = a + b;b = a - b;a = a - b;printf(“%d %dn“, a, b);return 0;这次就不太方便用倒酱油做比喻了:硬着头皮把醋倒在酱油瓶子里,然后分离出酱油倒回醋瓶子?比较理性的方法是手工模拟这段程序,看看每条语句

24、执行后的情况。在顺序结构程序中,程序一条一条依次执行。为了避免值和变量名混淆,假定用户输入的是 a0 和 b0,因此 scanf 语句执行完后 a = a0, b = b0。执行完 a = a+b 后:a = a0+b0, b = b0。执行完 b = a-b 后:a = a0+b0, b = a0。执行完 a = a-b 后:a = b 0, b = a0。这样就不难理解两个变量是如何交换的了。这个方法看起来很好(少用一个变量),但实际上很少使用,因为它的适用范围很窄:只有定义了加减法的数据类型才能这么做 1。事实上,笔者并不推荐读者采用这样的技巧实现变量交换:三变量法已经足够好了,这个例子

25、只是帮助读者提高程序阅读能力。提示 1-12:交换两个变量的三变量法适用范围广,推荐使用。那么是不是说,三变量法是解决本题的最佳途径了呢?答案是否定的。多数算法竞赛采用黑盒测试,即只考查程序解决问题的能力,而不关心它采用的什么方法。对于本题而言,最合适的程序莫过于:程序 1-10 变量交换( 3)#includeint main()int a, b;scanf(“%d%d“, printf(“%d %dn“, b, a);return 0;换句话说,我们的目标是解决问题,而不是为了写程序而写程序,同时应保持简单(Keep It Simple and Stupid,KISS),而不是自己创造条件

26、去展示编程技巧。提示 1-13:算法竞赛是在比谁能更好地解决问题,而不是在比谁写的程序看上去更高级。1 这个思想还有一个“变种”:用异或运算代替加法和减法,它还可以进一步简写成 a=b=a=b。但不建议使用。第 1 章 程序设计入门 1.4 分支结构程序设计例题 1-4 鸡兔同笼已知鸡和兔的总数量为 n,总腿数为 m。输入 n 和 m,依次输出鸡的数目和兔的数目。如果无解,则输出“No answer”(不要引号)。样例输入:14 32样例输出:12 2样例输入:10 16样例输出:No answer【分析】设鸡有 a 只,兔有 b 只,则 a+b=n,2a+4 b=m,联立解得 a=(4n-m

27、)/2,b=n- a。在什么情况下此解“不算数”呢?首先,a 和 b 都是整数;其次,a 和 b 必须是非负的。可以通过下面的程序判断:程序 1-11 鸡兔同笼#includeint main()int a, b, n, m;scanf(“%d%d“, a = (4*n-m)/2;b = n-a;if(m % 2 = 1 | a int main()int a, b, c;scanf(“%d%d%d“, if(a int main() int a, b, c;scanf(“%d%d%d“, if(a b,则交换 a 和 b(利用前面讲过的三变量交换法);接下来检查 a 和 c,最后检查 b 和

28、c,程序如下:程序 1-14 三整数排序( 3)#includeint main()int a, b, c, t;scanf(“%d%d%d“, if(a b) t = a; a = b; b = t; if(a c) t = a; a = c; c = t; if(b c) t = b; b = c; c = t; printf(“%d %d %dn“, a, b, c);return 0;为什么这样做是对的呢?因为经过第一次检查以后,必然有 ab,而第二次检查以后ac。由于第二次检查以后 a 的值不会变大,所以 ab 依然成立。换句话说,a 已经是 3 个数中的最小值。接下来只需检查 b

29、和 c 的顺序即可。一个很自然的问题产生了:其他检查顺序是否也可以呢?例如先(a,b),然后(b,c) ,最后(a,c)?这个问题留给读者思考。提示:上机实验。注意上面的程序中唯一的新东西:花括号。前面讲过,if 语句中有一个“语句 1”和可选的“语句 2”,且都要以分号结尾。有一种特殊的“语句”是由花括号括起来的多条语句。这多条语句可以作为一个整体,充当 if 语句中的“语句 1”或“语句 2”,且后面不需要加分号。当然,当 if 语句的条件满足时,这些语句依然会按顺序逐条执行,和普通的顺序结构一样。提示 1-19:可以用花括号把若干条语句组合成一个整体。这些语句仍然按顺序执行。最后一种思路

30、再次利用了“问题求解”这一目标它实际上并没有真的进行排序:求出了最小值和最大值,中间值是可以计算出来的。程序 1-15 三整数排序( 4)#includeint main()int a, b, c, x, y, z;scanf(“%d%d%d“, x = a; if(b z) z = b; if(c z) z = c;y = a + b + c - x - z;printf(“%d %d %dn“, x, y, z);return 0;注意程序中包含的“当前最小值”x 和“当前最大值”z。它们都初始化为 a,但是随着算法竞赛入门经典“比较”操作的进行而慢慢更新,最后变成真正的最小值和最大值。这

31、个技巧极为实用。提示 1-20:在难以一次性求出最后结果时,可以用变量储存“临时结果”,从而逐步更新。1.5 小结与习题经过前几个小节的学习,相信读者已经初步了解顺序结构程序设计和分支结构程序设计的核心概念和方法,然而对这些知识进行总结,并且完成适当的练习是很必要的。首先,我们给出一些实验,旨在加深读者对本章内容的认识。1.5.1 数据类型实验实验 A1:表达式 11111*11111 的值是多少?把 5 个 1 改成 6 个 1 呢?9 个 1 呢?实验 A2:把实验 A1 中的所有数换成浮点数,结果如何? 实验 A3:表达式 sqrt(-10)的值是多少?尝试用各种方式输出。在计算的过程中

32、系统会报错吗?实验 A4:表达式 1.0/0.0、0.0/0.0 的值是多少?尝试用各种方式输出。在计算的过程中系统会报错吗?实验 A5:表达式 1/0 的值是多少?在计算的过程中系统会报错吗?你不必解释背后的原因,但需注意现象。1.5.2 scanf 输入格式实验如果用语句 scanf(“%d%d”, else y+的确切含义是什么?这个 else 应和哪个 if 配套?有没有办法明确表达出配套方法,以避免初学者为之困惑? 1.5.5 小结对于不少读者来说,本章的内容都是直观、容易理解的,但这并不意味着所有人都能很快地掌握所有内容。相反,一些勤于思考的人反而更容易对一些常人没有注意到的细节问

33、题产生疑惑。对此,笔者提出如下两条建议。一是重视实验。哪怕不理解背后的道理,至少要清楚现象。例如,读者若亲自完成了本章的探索性实验和上机练习,一定会对整数范围、浮点数范围和精度、特殊的浮点值、scanf 对空格、TAB 和回车符的过滤、三角函数使用弧度而非角度等知识点有一定的了解。这些东西都没有必要死记硬背,但一定要学会实验的方法。这样即使编程时忘记了一些细节,手边又没有参考资料,也能轻松得出正确的结论。二是学会模仿。本章始终没有介绍#include语句的作用,但这丝毫不影响读者编写简单的程序。这看似是在鼓励读者“不求甚解”,但实为考虑到学习规律而作出的决策:初学者自学和理解能力不够,自信心也

34、不够,不适合在动手之前被灌输大量的理论。如果初学者在一开始就被告知“stdio 是 standard I/O 的缩写,stdio.h 是一个头文件,它在 XXX 位置,包含了XXX、 XXX、 XXX 等类型的函数,可以方便地完成 XXX、XXX、XXX 的任务;但其实这个头文件只是包含了这些函数的声明,还有一些宏定义,而真正的函数定义是在库中,编译时用不上,而在连接时”,多数读者会茫然不知所云,甚至自信心会受到打击,对学习 C 语言失去兴趣。正确的处理方法是“抓住主要矛盾”始终把学习、实验的焦点集中在最有趣的部分。如果直观的解决方案行得通,就不必追究其背后的机理。如果对一个东西不理解,就不要

35、对其进行修改;如果非改不可,则应根据自己的直觉和猜测尝试各种改法,而不必过多地思考“为什么要这样”。当然,这样的策略并不一定持续很久。当学生有一定的自学、研究能力之后,本书会在适当的时候解释一些重要的概念和原理,并引导学生寻找更多的资料进一步学习。要想把事情做好,必须学得透彻但没有必要操之过急。1.5.6 上机练习程 序 设 计 是 一 门 实 践 性 很 强 的 学 科 , 读 者 应 在 继 续 学 习 之 前 确 保 下 面 的 题 目 都 能 做 出 来 。习题 1-1 平均数(average)输入 3 个整数,输出它们的平均值,保留 3 位小数。习题 1-2 温度(temperatu

36、re)输入华式温度 f,输出对应的摄氏温度 c,保留 3 位小数。提示:c=5(f-32)/9。习题 1-3 连续和(sum)输入正整数 n,输出 1+2+n 的值。提示:目标是解决问题,而不是练习编程。习题 1-4 正弦和余弦(sincos)输入正整数 n(n2 int main()3 4 int i, n;5 scanf(“%d“, 6 for(i = 1; i #includeint main()int a,b,n; double m;for (a = 1; a int main()int x, n, hi, lo;for(x = 1; ;x+)if(n 9999) break;hi =

37、 n / 100; lo = n % 100;if(hi/10 = hi%10 return 0;此程序中的新东西是 continue 和 break 语句。continue 是指跳回 for 循环的开始,执行调整语句并判断循环条件(用大白话描述就是“直接进行下一次循环”),而 break 是指直接跳出循环 1。1 “逻辑与”;)就是一个死循环如果不采取措施(如 break),它就永远不会结束。2.2 循环结构程序设计例题 2-2 3n+1 问题猜想:对于任意大于 1 的自然数 n,若 n 为奇数,则将 n 变为 3n+1,否则变为 n 的一半。经过若干次这样的变换,一定会使 n 变为 1。例

38、如 3105168421。输入 n,输出变换的次数。n10。样例输入:3样例输出:7【分析】不难发现,程序完成的工作依然是重复性劳动:要么乘 3 加 1,要么除以 2,但和 2.1 节的程序又不太一样:循环的次数是不确定的,而且 n 也不是“递增”式的循环。这样的情况很适合用 while 循环来实现。程序 2-4 3n+1 问题#includeint main()int n, count = 0; scanf(“%d“, while(n 1)if(n % 2 = 1) n = n*3+l;else n /= 2;count+;printfA(“%dn, count);return 0;上面的程

39、序有好几个值得注意的地方。首先是那个“=0”,意思是,定义整型变量 count的同时初始化为 0,接下来是 while 语句。提示 2-7:while 循环的格式为“while(条件)循环体;”它看上去比 for 循环更简单。的确如此。事实上,可以用 while 改写 for。“for(初始化; 条件;调整)循环体;”等价于:初始化;while(条件 )循环体;调整;第 2 章 循环结构程序设计 建议读者再次利用 IDE 或者 gdb 跟踪调试,看看执行流程是怎样的。n/=2 的含义是 n=n/2,类似于前面介绍过的 i+。很多运算符都有类似的用法,例如 a*=3表示 a=a*3。count+

40、在语法上并不是新鲜事物,但这里要强调的是它的作用:计数器。由于最终输出的是变换的次数,需要一个变量来完成计数。提示 2-8:当需要 统计某种事物的个数时,可以用一个 变量来充当计数器。这个程序是正确的吗?先别急着下结论,让我们来测试一下。输入 987654321,看看结果是什么。很不幸,答案等于 1这明显是错误的。题目中给出的范围是 n10 9,这个987654321 是合法的输入数据。提示 2-9:不要忘 记测试。一个看上去正确的程序可能 隐含错误。问题出在哪里呢?反复阅读程序,我们仍然无法找到答案。既然观察无法解决问题,就动手实验吧!一种方法是利用 IDE 和 gdb 跟踪调试,但这并不是

41、本书所推荐的调试方法。一个更通用的方法是:输出中间结果。提示 2-10:在观察无法找出错误时,可以用“ 输出中间结果“ 的方法查错。在给 n 做变换的语句后加一条输出语句 printf(“%dn“,n),我们将很快找到问题的所在:第一次输出为-1332004332,它不大于 1,所以循环终止。如果认真完成了前面的所有探索实验,你将立刻明白这其中的缘由:乘法溢出了 1。例题 2-3 阶乘之和输入 n,计算 S=1!+2+3!+ 十 n!的末 6 位(不含前导 0)。n10 6。这里,n!表示前 n 个正整数之积。样例输入:10样例输出:37913【分析】这个任务并不难,引入累加变量 S 之后,核

42、心算法只有一句话:for(i=1;iint main()int i, j, n, S - 0;scanf(“%d“, for(i =1; i #include int main()const int MOD = 1000000; int i, j, n, S = 0; scanf(“%d“, sn); for(i = 1; i 25) n=25;”,效率和溢出都不成问题了。本节的两个例题展示了计数器、累加器的使用和循环结构程序设计中最常见的两个问 题:算术运算溢出和程序效率低下。这两个问题都不是那么容易解决的,将在后面章节中继续讨论。另外,本节中介绍的两个工具输出中间结果和计时函数,都是相当实

43、用的。2.3 文件操作例题 2-4 数据统计输入一些整数,求出它们的最小值、最大值和平均值(保留 3 位小数)。输入保证这 些数都是不超过 1000 的整数。样例输入:28 3 5 1 7 3 6样例输出:1 84.375【分析】如果是先输入整数 n,然后输入 n 个整数,相信读者能够写出程序来。关键在于:整数的个数是不确定的。下面直接给出程序:程序 2-7 数据统计 (错误#includeint main()int x, n = 0, min, max, s = 0;while(scanf(“%d“, if(x max) max=x; n+;printf(“%d %d %.3lfn“, mi

44、n, max, (double)s/n); return 0;1 Linux 下霈要输入 echo| ,因为在馱认情况下,当前目录不在可执行文件的搜索路径中.abc算法竞赛入门经典看看这个程序多了些什么东西?什么,scanf 函数有返回值?对,它返回的是成功输入的变量个数,不信可以翻阅库函数参考手册。当输入结束时,scanf 无法再次读取 x,将返回 0。下面该测试了。输入 2 8 3 5 1 7 3 6,按 Enter 键。怎么总不出结果?难道程序速度太慢?其实程序正在等待输入。还记得的输入格式吗?空格、TAB 和回车符都是无关紧要的,所以按Enter 键并不意味着输入的结束。那究竟如何才能

45、告诉程序输入结束了呢?提示 2-15:在 Windows 下,输入完毕后先按 Enter 键,再按 Ctrl+Z 键,最后再按 Enter 键, 即可结束输入。在 Linux 下,输入完 毕后按 Ctrl+D 键即可结束输入。好了,输入终于结束了,可是输出让我们大失所望:1 2293624 4.375。这个 2293624 是 从哪里来的?当我们用-O2 编译(按照惯例,不明白这是何物的读者请阅读附录)后答案变成了 1 10 4.375,和刚才不一样!换句话说:这个程序的运行结果是不确定的。在读者自己的机器上,答案甚至可能和上述两个都不同。根据“输出中间结果”的方法,读者不难验证下面的结论:变

46、量 max 在一开始就等于 2293624 (或者 10,自然无法更新为比它小的 8。提示 2-16:变量在未赋值之前的值是不确定的。特别地,它不一定等于 0。解决的方法就很清楚了:在使用之前赋初值。由于 min 保存的是最小值,它的初值应该是一个很大的数;反过来,max 的初值应该是一个很小的数。一种方法是定义一个很大的常数,如 INF=1000000000,然后让 max=INF,而 min=-INF,而另一种方法是先读取第一个整数 x,然后令 max=min=x。这样的好处是避免了人为的“假想无穷大”值,程序更加优美;而 INF 这样的常数有时还会引起其他问题,如“无限大不够大”,或者“

47、运算溢出”,后面还会继续讨论这个问题。上面的程序并不是很方便:每次测试都要手工输入许多数。尽管可以用前面讲的管道的方法,但数据只是保存在命令行中,仍然不够方便。个好的方法是用文件把输入数据保存在文件中,输出数据也保存在文件中。这样,只要事先把输入数据保存在文件中,就不必每次重新输入了;数据输出在文件中也避免了“输出太多,一卷屏前面的就看不见了”这样的尴尬运行结束后,慢慢浏览输出文件即可。如果有标准答案文件,还可以进行文件比较 1,而无须用眼睛检査输出是否正确。 事实上,几乎所有算法竞赛的输入数据和标准答案都是保存在文件中的。使用文件最简单的方法是使用输入输出重定向,只需在 main 函数的入口

48、处加入以下两条语句:freopen(“input.txt“, “r“, stdin);freopen(“output.txt“, “w“, stdout) ;它将使得 scanf 从文件 input.txt 读入,printf 写入文件 output.txt。事实上,不只是 scanf 和printf,所有读键盘输入、写屏幕输出的函数都将改用文件。尽管这样做很方便,并不是所有算法竞赛都允许你用程序读写文件。甚至有的竞赛允许访问文件,但不允许你用 freopen 这样的重定向方式读写文件。请参赛之前仔细阅读文件读写的相关规定。提示 2-17:请在比赛之前了解文件读写的相关规定:是标准输入输出(也

49、称标准 I/O,即直 接读键盘、写屏幕)还是文件输入输出?如果是文件输入输出,是否禁止用重定向方式访问文件?多年来,无数选手因文件相关问题丢掉了大量的得分。一个普适的原则是:详细阅读比赛规定,并严格遵守。例如,输入输出文件名和程序名往往都有着严格规定,不要弄错大小写,不要拼错文件名,不要使用绝对路径或相对路径。例如,如果题目规定程序名称为 test,输入文件名为 test.in,输出文件名为 test.out,就不要1在 Windows 中可以使用 fc 命令,而在 Linux 中可以使用 diff 命令。第 2 章 循环结构程序设计 犯以下错误。错误 1:程序存为 t1.c(应该改成 test.c)。错误 2:从 t

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报