描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302356288
作者授课资源:https://space.bilibili.com/208090093
如果你是一名程序员,如果你参加NOIP、NOI、ACM/ICPC竞赛,只要你对算法感兴趣,那就来吧!就是这本被*多程序员所喜爱、被大量学校广泛作为教材的算法竞赛经典之作!
算法竞赛入门经典一书全新改版,页码翻倍,奇葩?非也,这是因为:
*版内容太少,让人感觉意犹未尽。
有些内容有点过时,需要与时俱进。
C 的介绍太少,例题太少,学有余力的同学在入门完之后有些迷茫。
此次改版就是针对这些不足,所以很让人期待!
本书是一本算法竞赛的入门与提高教材,把C/C 语言、算法和解题有机地结合在一起,淡化理论,注重学习方法和实践技巧。全书内容分为12章,包括程序设计入门、循环结构程序设计、数组和字符串、函数和递归、C 与STL入门、数据结构基础、暴力求解法、高效算法设计、动态规划初步、数学概念与方法、图论模型与算法、高级专题等内容,覆盖了算法竞赛入门和提高所需的主要知识点,并含有大量例题和习题。书中的代码规范、简洁、易懂,不仅能帮助读者理解算法原理,还能教会读者很多实用的编程技巧;书中包含的各种开发、测试和调试技巧也是传统的语言、算法类书籍中难以见到的。
本书可作为全国青少年信息学奥林匹克联赛(NOIP)复赛教材、全国青少年信息学奥林匹克竞赛(NOI)和ACM国际大学生程序设计竞赛(ACM/ICPC)的训练资料,也可作为IT工程师与科研人员的参考用书。
第1部分 语言篇
第1章 程序设计入门…
1.1 算术表达式
1.2 变量及其输入
1.3 顺序结构程序设计
1.4 分支结构程序设计
1.5 注解与习题
1.5.1 C语言、C99、C11及其他
1.5.2 数据类型与输入格式
1.5.3 习题
1.5.4 小结
第2章 循环结构程序设计…
2.1 for循环
2.2 while循环和do-while循环
2.3 循环的代价
2.4 算法竞赛中的输入输出框架
2.5 注解与习题
2.5.1 习题
2.5.2 小结
第3章 数组和字符串…
3.1 数组
3.2 字符数组
3.3 竞赛题目选讲
3.4 注解与习题
3.4.1 进位制与整数表示
3.4.2 思考题
3.4.3 黑盒测试和在线评测系统
3.4.4 例题一览与习题
3.4.5 小结
第4章 函数和递归…
4.1 自定义函数和结构体
4.2 函数调用与参数传递
4.2.1 形参与实参
4.2.2 调用栈
4.2.3 用指针作参数
4.2.4 初学者易犯的错误
4.2.5 数组作为参数和返回值
4.2.6 把函数作为函数的参数
4.3 递归
4.3.1 递归定义
4.3.2 递归函数
4.3.3 C语言对递归的支持
4.3.4 段错误与栈溢出
4.4 竞赛题目选讲
4.5 注解与习题
4.5.1 头文件、副作用及其他
4.5.2 例题一览和习题
4.5.3 小结
第5章 C 与STL入门…
5.1 从C到C
5.1.1 C 版框架
5.1.2 引用
5.1.3 字符串
5.1.4 再谈结构体
5.1.5 模板
5.2 STL初步
5.2.1 排序与检索
5.2.2 不定长数组:vector
5.2.3 集合:set
5.2.4 映射:map
5.2.5 栈、队列与优先队列
5.2.6 测试STL
5.3 应用:大整数类
5.3.1 大整数类BigInteger
5.3.2 四则运算
5.3.3 比较运算符
5.4 竞赛题目举例
5.5 习题
第2部分 基础篇
第6章 数据结构基础…
6.1 再谈栈和队列
6.2 链表
6.3 树和二叉树
6.3.1 二叉树的编号
6.3.2 二叉树的层次遍历
6.3.3 二叉树的递归遍历
6.3.4 非二叉树
6.4 图
6.4.1 用DFS求连通块
6.4.2 用BFS求短路
6.4.3 拓扑排序
6.4.4 欧拉回路
6.5 竞赛题目选讲
6.6 训练参考
第7章 暴力求解法…
7.1 简单枚举
7.2 枚举排列
7.2.1 生成1~n的排列
7.2.2 生成可重集的排列
7.2.3 解答树
7.2.4 下一个排列
7.3 子集生成
7.3.1 增量构造法
7.3.2 位向量法
7.3.3 二进制法
7.4 回溯法
7.4.1 八皇后问题
7.4.2 其他应用举例
7.5 路径寻找问题
7.6 迭代加深搜索
7.7 竞赛题目选讲
7.8 训练参考
第3部分 竞赛篇
第8章 高效算法设计…
8.1 算法分析初步
8.1.1 渐进时间复杂度
8.1.2 上界分析
8.1.3 分治法
8.1.4 正确对待算法分析结果
8.2 再谈排序与检索
8.2.1 归并排序
8.2.2 快速排序
8.2.3 二分查找
8.3 递归与分治
8.4 贪心法
8.4.1 背包相关问题
8.4.2 区间相关问题
8.4.3 Huffman编码
8.5 算法设计与优化策略
8.6 竞赛题目选讲
8.7 训练参考
第9章 动态规划初步…
9.1 数字三角形
9.1.1 问题描述与状态定义
9.1.2 记忆化搜索与递推
9.2 DAG上的动态规划
9.2.1 DAG模型
9.2.2 长路及其字典序
9.2.3 固定终点的长路和短路
9.2.4 小结与应用举例
9.3 多阶段决策问题
9.3.1 多段图的短路
9.3.2 0-1背包问题
9.4 更多经典模型
9.4.1 线性结构上的动态规划
9.4.2 树上的动态规划
9.4.3 复杂状态的动态规划
9.5 竞赛题目选讲
9.6 训练参考
第10章 数学概念与方法…
10.1 数论初步
10.1.1 欧几里德算法和分解定理
10.1.2 Eratosthenes筛法
10.1.3 扩展欧几里德算法
10.1.4 同余与模算术
10.1.5 应用举例
10.2 计数与概率基础
10.2.1 杨辉三角与二项式定理
10.2.2 数论中的计数问题
10.2.3 编码与解码
10.2.4 离散概率初步
10.3 其他数学专题
10.3.1 递推
10.3.2 数学期望
10.3.3 连续概率
10.4 竞赛题目选讲
10.5 训练参考
第11章 图论模型与算法…
11.1 再谈树
11.1.1 无根树转有根树
11.1.2 表达式树
11.2 小生成树
11.2.1 Kruskal算法
11.2.2 竞赛题目选解
11.3 短路问题
11.3.1 Dijkstra算法
11.3.2 Bellman-Ford算法
11.3.3 Floyd算法
11.3.4 竞赛题目选讲
11.4 网络流初步
11.4.1 流问题
11.4.2 增广路算法
11.4.3 小割流定理
11.4.4 小费用流问题
11.4.5 应用举例
11.5 竞赛题目选讲
11.6 训练参考
11.7 总结与展望
第12章 高级专题…
12.1 知识点选讲
12.1.1 自动机
12.1.2 树的经典问题和方法
12.1.3 可持久化数据结构
12.1.4 多边形的布尔运算
12.2 难题选解
12.2.1 数据结构
12.2.2 网络流
12.2.3 数学
12.2.4 几何
12.2.5 非完美算法
12.2.6 杂题选讲
12.3 小结与习题
附录A 开发环境与方法…
A.1 命令行
A.1.1 文件系统
A.1.2 进程
A.1.3 程序的执行
A.1.4 重定向和管道
A.1.5 常见命令
A.2 操作系统脚本编程入门
A.2.1 Windows下的批处理
A.2.2 Linux下的Bash脚本
A.2.3 再谈随机数
A.3 编译器和调试器
A.3.1 gcc的安装和测试
A.3.2 常见编译选项
A.3.3 gdb简介
A.3.4 gdb的高级功能
A.4 浅谈IDE
主要参考书目
第2版前言
《算法竞赛入门经典》第1版出版至今已有四个年头。这四年间发生了很多变化,如NOI系列比赛终于对STL“解禁”,如C11和C 11标准出台,g 编译器升级(直接导致本书第1版中官方使用的和>?运算符无法编译通过),如《算法竞赛入门经典——训练指南》的出版弥补了本书第1版的很多缺憾,再如ACM/ICPC的蓬勃发展,使更多的大学生了解并参与到了算法竞赛中来……
看来,是时候给本书“升级”了。
主要的变化
我原本打算只是增加一章专门介绍C 和STL,用符合新语言规范的方式重写部分代码,顺便增加一些例题和习题,没想到一写就是100页——几乎让书的篇幅翻了一倍。写作第1版时,220页的篇幅是和诸位一线中学教师商量后定下来的,因为书太厚会让初学者望而生畏。不过这几年的读者反馈让我意识到:由于篇幅限制,太多的东西让读者意犹未尽,还不如多写点。虽然之后出版了《算法竞赛入门经典——训练指南》,但那本书的主要目标是补充知识点,即拓展知识宽度,而我更希望在知识宽度几乎不变的情况下增加深度——我眼中的竞赛应该主要比思维和实践能力,而不是主要比见识。
索性,我继续加大篇幅,用大量的例子(包括题目和代码)来表现我想向读者传达的信息。一位试读的朋友在收到份书稿片段时惊呼:“题目的质量比第1版提高太多了!”这正是我这次改版的主要目的。
具体来说,这次改版有以下变化:
q 在前4章中逐步介绍一些更实用的语言技巧,直接使用竞赛题目作为例子。
q 全新的第5章,讲解竞赛中常用的C 语法,包括STL算法和容器。
q 第6~7章作为基础篇,加大代码和技巧的比例,并适当增加例题。
q 第8~11章作为中级篇,增加了各种例题,着重锻炼思维能力。
q 全新的第12章作为高级篇,在《算法竞赛入门经典——训练指南》的基础上补充少量知识点与大量精彩例题。
需要特别说明的是第12章出现的原因。这一章的内容很难,而且要求读者熟练掌握《算法竞赛入门经典——训练指南》的主要内容,看起来和“入门”二字是矛盾的。其实本书虽然名为“入门经典”,实际上却不仅只适合入门读者。根据这几年读者反馈的情况来看,有相当数量的有经验的选手也购买了本书。原因在于:很多有经验的选手属于“自学成才”,总觉得自己可能会漏掉点什么基础知识。事实也是如此:本书中提到的很多代码和分析技巧是传统教科书中见不到的,对于很多有经验的选手来说也是“新鲜事物”,并且他们能比初学者更快、更好地把这些知识运用到比赛中去。本书第12章就是为这些读者准备的。如果这样解释还不够直观,就把第12章作为一个游戏里通关后多出来的Hard模式吧!
阅读说明
既然内容有了较大变化,阅读方式也需要再次说明一下。首先,和本书第1版一样,本书好是有人带着学习,如老师、教练或者学长。随着网络的发展,这个条件越来越容易满足了——就算是没人指导,也可以在别人的博客中留言,或者在贴吧中寻求帮助。
一定要重视书中的“提示”。书中有很多“提示”部分都是非常重要的知识点或者技巧,有些提示看似平凡无奇,但如果没有引起重视而导致赛场上丢分,可是会追悔莫及的。
接下来是关于新增第5章的。首先声明一点,这一章并不是C 语言速成——C 语言是不可能速成的。这一章不是说你从头读到尾然后就掌握C 了,而是提供一个纲要,告诉你哪些东西是算法竞赛中常用的,以及这些东西应当如何使用。你可以先另外找一本书(或者阅读网上的文章)学习C ,然后再看本书第5章(目的是把那些又容易晕又不那么有用的知识从脑子里删除),也可以直接看本书第5章,每次遇到看不懂或者觉得不够详细的地方,再找其他参考书来学。顺便说一句,就算你已经非常熟悉C 了,也好浏览一下第5章(特别是代码!)。这不会花费太多时间,但很可能学到有用的东西。
忍不住再说点题外话。有时学习算法的好方法并不是编写程序,而是手算。“手算”这个词听上去有点枯燥,改成“玩游戏”如何?如《雷顿教授与不可思议的小镇》就是一个不错的选择——它包含了过河问题(谜题7、93)、找砝码(谜题6、131)、一笔画(谜题30、39)、n皇后(谜题80~83,130)、倒水问题(谜题23、24、78)、幻方(谜题95)、华容道(谜题97、132、135)等诸多经典问题。
致谢
虽然多出来了200多页,其实本书的改版工作并没有花费太长时间(不到半年),在此期间也没有麻烦太多朋友读稿和讨论。参与本书第2版读稿和校对工作的几位朋友分别是:陈锋(第8~11章)、王玉斌(第8~9章,第12章)、郭云镝(第12章)、曹海宇(第5章、第9章)、陈立杰(第12章)、叶子卿(第12章)、周以凡(第12章)。
感谢给我发邮件以及在googlecode的wiki中留言指出本书第1版勘误的网友们:imxivid、zr95.vip、李智维、王玉、chnln0526、yszhou4tech、metowolf88、zhongying822、chong97993、tplee923、wtx20074587、chu.pang等,你们的支持和鼓励是我写作的重要动力。
另外,书中部分难题的题解离不开以下朋友的赐教和讨论:Md.Mahbubul Hasan、Shahriar Manzoor、Derek Kisman、Per Austrin、Luis Garcia、顾昱洲、陈立杰、张培超等。
第2版的习题全部(这次不仅仅是“主要”了)来自UVa在线评测系统,感谢Miguel Revilla教授、他的儿子Miguel Jr.和Carlos M. Casas Cuadrado对本书的大力支持。
后,再次感谢清华大学出版社的朱英彪编辑在这个恰当的时机提出改版事宜,并容忍我把交稿时间一拖再拖。希望这次改版不会让你失望。
刘汝佳
推荐序一
《算法竞赛入门经典(第2版)》要面世了。一方面高兴,一方面也想借题发挥,这是因为近年来我和我的团队致力于研究计算机教育的改革,对于应该如何提升学生的思维能力和行动能力有了新的认识。当然我会把握“不要离题太远”。
在我的书案上常年摆着一本蓝皮的书《算法艺术与信息学竞赛》,这是刘汝佳与黄亮合写的书,2003年12月我怀着喜悦的心情给这本书写了一页纸的序言。今天,时隔十年,我又拿起笔来为汝佳的新书作序,想到信息学奥林匹克的魅力,看到我们的学生能够承担起普及的责任和水平,此时此刻我的欣喜之情难以言表。“青出于蓝更胜于蓝”是我们当老师的*愿望和期盼。汝佳之所以能写出这种内容和内涵丰富,文字也很难表达的思维艺术之美的好书,在于他对于信息学竞赛的热爱和他在青少年中普及计算机知识的强烈的责任感。汝佳为人低调诚实,做事认真负责,*可贵之处是那种“打破砂锅问到底”的求真务实精神,还有就是愿意和善于与人合作共事,能够真心听取别人的意见。
刘汝佳在中学参加信息学奥赛,进入清华大学后作为主力队员参加过ACM/ICPC世界大学生程序设计大赛,在本科和读研期间又长期担任国际信息学奥林匹克中国队的教练。很早以前他就说过:想写一本“从入门开始就能陪伴着读者的书”,意思是书的作用不仅是答疑、解惑,更是像朋友和知己,同读者一起探讨和研究问题。我当然赞成著书者的境界,在我给本科生上“程序设计基础”课时的感悟是:教学相长,发挥学生在学习中的主体作用,激发兴趣和调动积极性,创造条件,使其参与课程内容的研讨,并提出宝贵意见,是成就精品课的必由之路。汝佳说:“书的第12章就像是一个路标,告诉你每条路通往怎样的风景,但是具体还得靠读者自己走过去,在走之前也需要自己选择。”话说得很到位。闪光的东西蕴含在解决问题时的那些思维之美中,精妙的解题思路和策略有时令人拍案叫绝,一路学一路体味赏心悦目的风景,没有不爱学和学不会之理。
学会编程是一件相当重要的事,我在清华上课时对学生说“这是你们的看家本事”。一个国家,一个民族,要想不落伍,要想跻身于世界民族之林,关键在于拥有高素质的人才。学习和掌握信息科学与技术,在高水准人才的知识结构中占有重要的地位。讲文化要以科学为基础,讲科学要提高到文化的高度。学习计算机必须了解这一学科的内在规律和特征,“构造性”和“能行性”是计算机学科的两个*根本特征。与构造性相应的构造思维,又称计算思维,指的是通过算法的“构造”和实现来解决一个给定问题的一种“能行”的思维方式。
有些问题没有固定的解法,给读者留有广阔的发挥创造力的空间,经过思考构造出的算法能不能高效地解决问题,都得通过上机实践的检验,在这一过程中思维能力和行动能力会同步提升。我认为高手应该是这样炼成的。光说不练,纸上谈兵是*学不会的。
当前,有识之士已经认识到:大学计算机作为基础课,与数学、物理同样重要。培养计算机的应用能力,掌握使用计算机的思路和方法,必须既动手又动脑,体会和感悟蕴含于其中的计算思维要素,对于现代人是非常重要的。
当你拿到这本书时,建议你先看“阅读说明”。其中有两点,一是“本书*好是有人带着学习”,如果这一点做不到的话,建议你求助于网络,开展合作学习,向高人请教,让心得共享,这些都符合现代学习理念;二是“一定要重视书中的提示”,因为其中包含着需要掌握的重要知识点和编程技巧,你会发觉有些内容在一般教科书中是看不到的。
这是一本学习竞赛入门的书。我想说的是参加信息学竞赛入门不难,深造也是做得到的,关键是专心、恒心与信心,世上无难事,只要肯攀登!
全国信息学奥林匹克(NOI)科学委员会名誉主席
国际信息学奥林匹克中国队总教练
清华大学计算机科学与技术系
吴文虎
第9章 动态规划初步
学习目标
理解状态和状态转移方程
理解子结构和重叠子问题
熟练运用递推法和记忆化搜索求解数字三角形问题
熟悉DAG上动态规划的常见思路、两种状态定义方法和刷表法
掌握记忆化搜索在实现方面的注意事项
掌握记忆化搜索和递推中输出方案的方法
掌握递推中滚动数组的使用方法
熟练解决经典动态规划问题
动态规划的理论性和实践性都比较强,一方面需要理解“状态”、“状态转移”、“子结构”、“重叠子问题”等概念,另一方面又需要根据题目的条件灵活设计算法。可以这样说,对动态规划的掌握情况在很大程度上能直接影响一个选手的分析和建模能力。
9.1 数字三角形
动态规划是一种用途很广的问题求解方法,它本身并不是一个特定的算法,而是一种思想,一种手段。下面通过一个题目阐述动态规划的基本思路和特点。
9.1.1 问题描述与状态定义
数字三角形问题。有一个由非负整数组成的三角形,行只有一个数,除了下行之外每个数的左下方和右下方各有一个数,如图9-1所示。
(a)数字三角形 (b)格子编号
图9-1 数字三角形问题
从行的数开始,每次可以往左下或右下走一格,直到走到下行,把沿途经过的数全部加起来。如何走才能使得这个和尽量大?
【分析】
如果熟悉回溯法,可能会立刻发现这是一个动态的决策问题:每次有两种选择——左下或右下。如果用回溯法求出所有可能的路线,就可以从中选出路线。但和往常一样,回溯法的效率太低:一个n层数字三角形的完整路线有2n-1条,当n很大时回溯法的速度将让人无法忍受。
为了得到高效的算法,需要用抽象的方法思考问题:把当前的位置(i, j)看成一个状态(还记得吗?),然后定义状态(i, j)的指标函数d(i, j)为从格子(i, j)出发时能得到的和(包括格子(i, j)本身的值)。在这个状态定义下,原问题的解是d(1, 1)。
下面看看不同状态之间是如何转移的。从格子(i, j)出发有两种决策。如果往左走,则走到(i 1, j)后需要求“从(i 1, j)出发后能得到的和”这一问题,即d(i 1, j)。类似地,往右走之后需要求解d(i 1, j 1)。由于可以在这两个决策中自由选择,所以应选择d(i 1,j)和d(i 1,j 1)中较大的一个。换句话说,得到了所谓的状态转移方程:
如果往左走,那么好情况等于(i, j)格子里的值a(i, j)与“从(i 1, j)出发的总和”之和,此时需注意这里的“”二字。如果连“从(i 1,j)出发走到底部”这部分的和都不是的,加上a(i, j)之后肯定也不是的。这个性质称为子结构(optimal substructure),也可以描述成“全局解包含局部解”。不管怎样,状态和状态转移方程一起完整地描述了具体的算法。
提示9-1:动态规划的核心是状态和状态转移方程。
9.1.2 记忆化搜索与递推
有了状态转移方程之后,应怎样计算呢?
方法1:递归计算。程序如下(需注意边界处理):
int solve(int i, int j){
return a[i][j] (i == n ? 0 : max(solve(i 1,j),solve(i 1,j 1)));
}
图9-2 重叠子问题
文本框:
图9-2 重叠子问题
这样做是正确的,但时间效率太低,其原因在于重复计算。
如图9-2所示为函数solve(1, 1)对应的调用关系树。看到了吗?solve(3, 2)被计算了两次(一次是solve(2, 1)需要的,一次是solve(2, 2)需要的)。也许读者会认为重复算一两个数没有太大影响,但事实是:这样的重复不是单个结点,而是一棵子树。如果原来的三角形有n层,则调用关系树也会有n层,一共有2n-1个结点。
提示9-2:用直接递归的方法计算状态转移方程,效率往往十分低下。其原因是相同的子问题被重复计算了多次。
方法2:递推计算。程序如下(需再次注意边界处理):
int i, j;
for(j = 1; j <= n; j ) d[n][j] = a[n][j];
for(i = n-1; i >= 1; i–)
for(j = 1; j <= i; j )
d[i][j] = a[i][j] max(d[i 1][j],d[i 1][j 1]);
程序的时间复杂度显然是O(n2),但为什么可以这样计算呢?原因在于:i是逆序枚举的,因此在计算d[i][j]前,它所需要的d[i 1][j]和d[i 1][j 1]一定已经计算出来了。
提示9-3:可以用递推法计算状态转移方程。递推的关键是边界和计算顺序。在多数情况下,递推法的时间复杂度是:状态总数×每个状态的决策个数×决策时间。如果不同状态的决策个数不同,需具体问题具体分析。
方法3:记忆化搜索。程序分成两部分。首先用“memset(d,-1,sizeof(d));”把d全部初始化为-1,然后编写递归函数[1]:
int solve(int i, int j){
if(d[i][j] >= 0) return d[i][j];
return d[i][j] = a[i][j] (i == n ? 0 : max(solve(i 1,j),solve(i 1,j 1)));
}
上述程序依然是递归的,但同时也把计算结果保存在数组d中。题目中说各个数都是非负的,因此如果已经计算过某个d[i][j],则它应是非负的。这样,只需把所有d初始化为-1,即可通过判断是否d[i][j]≥0得知它是否已经被计算过。
图9-3 记忆化搜索
文本框:
图9-3 记忆化搜索
后,千万不要忘记在计算之后把它保存在d[i][j]中。根据C语言“赋值语句本身有返回值”的规定,可以把保存d[i][j]的工作合并到函数的返回语句中。
上述程序的方法称为记忆化(memoization),它虽然不像递推法那样显式地指明了计算顺序,但仍然可以保证每个结点只访问一次,如图9-3所示。
由于i和j都在1~n之间,所有不相同的结点一共只有O(n2)个。无论以怎样的顺序访问,时间复杂度均为O(n2)。从2
评论
还没有评论。