5.5 习题

本章是语言篇的最后一章,介绍了很多可选但是有用的C++语言特性和库函数。有些库函数实际上已经涉及后面要介绍的算法和数据结构,但是在学习原理之前,仍然可以先练习使用这些函数。

如表5-1所示是例题列表,其中前9道题是必须掌握的。后面3题虽然相对比较复杂,但是也强烈建议读者试一试,锻炼编程能力。

表5-1 例题列表

 

  类别     题号     题目名称(英文)     备注  
  例题5-1     UVa10474     Where is the Marble?     排序和查找  
  例题5-2     UVa101     The Blocks Problem     vector的使用  
  例题5-3      UVa10815     Andy's First Dictionary     set的使用  
  例题5-4     UVa156     Ananagrams     map的使用  
  例题5-5     UVa12096     The SetStack Computer     stack与STL其他容器的综合运用  
  例题5-6     UVa540     Team Queue     queue与STL其他容器的综合运用  
  例题5-7     UVa136     Ugly Numbers     priority_queue的使用  
  例题5-8     UVa400     Unix ls     排序和字符串处理  
  例题5-9     UVa1592     Database     map的妙用  
  例题5-10     UVa207     PGA Tour Prize Money     排序和其他细节处理  
  例题5-11     UVa814     The Letter Carrier's Rounds     字符串以及STL容器的综合运用  
  例题5-12     UVa221     Urban Elevations     离散化  

本章的习题主要是为了练习C++语言以及STL,程序本身并不一定很复杂。建议读者至少完成8道习题。如果想达到更好的效果,建议完成12题或更多。

习题5-1 代码对齐(Alignment of Code, ACM/ICPC NEERC 2010, UVa1593)

输入若干行代码,要求各列单词的左边界对齐且尽量靠左。单词之间至少要空一格。每个单词不超过80个字符,每行不超过180个字符,一共最多1000行,样例输入与输出如图5-5所示。

图5-5 对齐代码的样例输入与输出

习题5-2 Ducci序列(Ducci Sequence, ACM/ICPC Seoul 2009, UVa1594)

对于一个n元组(a1, a2, …, an),可以对于每个数求出它和下一个数的差的绝对值,得到一个新的n元组(|a1-a2|, |a2-a3|, …, |an-a1|)。重复这个过程,得到的序列称为Ducci序列,例如:

(8, 11, 2, 7) -> (3, 9, 5, 1) -> (6, 4, 4, 2) -> (2, 0, 2, 4) -> (2, 2, 2, 2) -> (0, 0, 0, 0).

也有的Ducci序列最终会循环。输入n元组(3≤n≤15),你的任务是判断它最终会变成0还是会循环。输入保证最多1000步就会变成0或者循环。

习题5-3 卡片游戏(Throwing cards away I, UVa 10935)

桌上有nn≤50)张牌,从第一张牌(即位于顶面的牌)开始,从上往下依次编号为1~n。当至少还剩下两张牌时进行以下操作:把第一张牌扔掉,然后把新的第一张牌放到整叠牌的最后。输入每行包含一个n,输出每次扔掉的牌以及最后剩下的牌。

习题5-4 交换学生(Foreign Exchange, UVa 10763)

n(1≤n≤500000)个学生想交换到其他学校学习。为了简单起见,规定每个想从A学校换到B学校的学生必须找一个想从B换到A的“搭档”。如果每个人都能找到搭档(一个人不能当多个人的搭档),学校就会同意他们交换。每个学生用两个整数A、B表示,你的任务是判断交换是否可以进行。

习题5-5 复合词(Compound Words, UVa 10391)

给出一个词典,找出所有的复合词,即恰好有两个单词连接而成的单词。输入每行都是一个由小写字母组成的单词。输入已按照字典序从小到大排序,且不超过120000个单词。输出所有复合词,按照字典序从小到大排列。

习题5-6 对称轴(Symmetry, ACM/ICPC Seoul 2004, UVa1595)

给出平面上NN≤1000)个点,问是否可以找到一条竖线,使得所有点左右对称。例如图5-6中,左边的图形有对称轴,右边没有。

 

图5-6 对称轴

习题5-7 打印队列(Printer Queue, ACM/ICPC NWERC 2006, UVa12100)

学生会里只有一台打印机,但是有很多文件需要打印,因此打印任务不可避免地需要等待。有些打印任务比较急,有些不那么急,所以每个任务都有一个1~9间的优先级,优先级越高表示任务越急。

打印机的运作方式如下:首先从打印队列里取出一个任务J,如果队列里有比J更急的任务,则直接把J放到打印队列尾部,否则打印任务J(此时不会把它放回打印队列)。

输入打印队列中各个任务的优先级以及所关注的任务在队列中的位置(队首位置为0),输出该任务完成的时刻。所有任务都需要1分钟打印。例如,打印队列为{1, 1, 9, 1, 1, 1},目前处于队首的任务最终完成时刻为5。

习题5-8 图书管理系统(Borrowers, ACM/ICPC World Finals 1994, UVa230)

你的任务是模拟一个图书管理系统。首先输入若干图书的标题和作者(标题各不相同,以END结束),然后是若干指令:BORROW指令表示借书,RETURN指令表示还书,SHELVE指令表示把所有已归还但还未上架的图书排序后依次插入书架并输出图书标题和插入位置(可能是第一本书或者某本书的后面)。

图书排序的方法是先按作者从小到大排,再按标题从小到大排。在处理第一条指令之前,你应当先将所有图书按照这种方式排序。

习题5-9 找bug(Bug Hunt, ACM/ICPC Tokyo 2007, UVa1596)

输入并模拟执行一段程序,输出第一个bug所在的行。每行程序有两种可能:

 

赋值语句可能会出现两种bug:下标index越界;使用未初始化的变量(index和value都可能出现这种情况)。

程序不超过1000行,每行不超过80个字符且所有常数均为小于231的非负整数。

习题5-10 在Web中搜索(Searching the Web, ACM/ICPC Beijing 2004, UVa1597)

输入n篇文章和m个请求(n<100,m≤50000),每个请求都是以下4种格式之一。

 

处理询问时,需要对于每篇文章输出证据。前3种询问输出所有至少包含一个关键字的行,第4种询问输出整篇文章。关键字只由小写字母组成,查找时忽略大小写。每行不超过80个字符,一共不超过1500行。

本题有一定实际意义,并且能锻炼编码能力,建议读者一试。

习题5-11 更新字典(Updating a Dictionary, UVa12504)

在本题中,字典是若干键值对,其中键为小写字母组成的字符串,值为没有前导零或正号的非负整数(-4,03和+77都是非法的,注意该整数可以很大)。输入一个旧字典和一个新字典,计算二者的变化。输入的两个字典中键都是唯一的,但是排列顺序任意。具体格式为(注意字典格式中不含任何空白字符):

{key:value,key:value,…,key:value}

输入包含两行,各包含不超过100个字符,即旧字典和新字典。输出格式如下:

 

例如,若输入两行分别为{a:3,b:4,c:10,f:6}和{a:3,c:5,d:10,ee:4},输出为以下3行:+d,ee;-b,f;*c。

习题5-12 地图查询(Do You Know The Way to San Jose?, ACM/ICPC World Finals 1997, UVa511)

n张地图(已知名称和某两个对角线端点的坐标)和m个地名(已知名称和坐标),还有q个查询。每张地图都是边平行于坐标轴的矩形,比例定义为高度除以宽度的值。每个查询包含一个地名和详细等级i。面积相同的地图总是属于同一个详细等级。假定包含此地名的地图中一共有k种不同的面积,则合法的详细等级为1~k(其中1最不详细,k最详细,面积越小越详细)。如果详细等级i的地图不止一张,则输出地图中心和查询地名最接近的一张;如果还有并列的,地图长宽比应尽量接近0.75(这是Web浏览器的比例);如果还有并列,查询地名和地图右下角的坐标应最远(对应最少的滚动条移动);如果还有并列,则输出x坐标最小的一个。如果查询的地名不存在或者没有地图包含它,或者包含它的地图总数超过i,应报告查询非法(并输出包含它的最详细地图名称,如果存在)。

提示:本题的要求比较细致,如果打算编程实现,建议参考原题。

习题5-13 客户中心模拟(Queue and A, ACM/ICPC World Finals 2000, UVa822)

你的任务是模拟一个客户中心运作情况。客服请求一共有n(1≤n≤20)种主题,每种主题用5个整数描述:tid, num, t0, t, dt,其中tid为主题的唯一标识符,num为该主题的请求个数,t0为第一个请求的时刻,t为处理一个请求的时间,dt为相邻两个请求之间的间隔(为了简单情况,假定同一个主题的请求按照相同的间隔到达)。

客户中心有m(1≤m≤5)个客服,每个客服用至少3个整数描述:pid, k, tid1, tid2, …, tidk,表示一个标识符为pid的人可以处理k种主题的请求,按照优先级从大到小依次为tid1, tid2, …, tidk。当一个人有空时,他会按照优先级顺序找到第一个可以处理的请求。如果有多个人同时选中了某个请求,上次开始处理请求的时间早的人优先;如果有并列,id小的优先。输出最后一个请求处理完毕的时刻。

习题5-14 交易所(Exchange, ACM/ICPC NEERC 2006, UVa1598)

你的任务是为交易所设计一个订单处理系统。要求支持以下3种指令。

 

交易规则如下:对于当前买订单,若当前最低卖价(ask price)低于当前出价,则发生交易;对于当前卖订单,若当前最高买价(bid price)高于当前价格,则发生交易。发生交易时,按供需物品个数的最小值交易。交易后,需修改订单的供需物品个数。当出价或价格相同时,按订单产生的先后顺序发生交易。输入输出细节请参考原题。

提示:本题是一个不错的优先队列练习题。

习题5-15 Fibonacci的复仇(Revenge of Fibonacci, ACM/ICPC Shanghai 2011, UVa12333)

Fibonacci数的定义为:F(0)=F(1)=1,然后从F(2)开始,F(i)=F(i-1)+F(i-2)。例如,前10项Fibonacci数分别为1, 1, 2, 3, 5, 8, 13, 21, 34, 55……

有一天晚上,你梦到了Fibonacci,它告诉你一个有趣的Fibonacci数。醒来以后,你只记得了它的开头几个数字。你的任务是找出以它开头的最小Fibonacci数的序号。例如以12开头的最小Fibonacci数是F(25)。输入不超过40个数字,输出满足条件的序号。

如果序号小于100000的Fibonacci数均不满足条件,输出-1。

提示:本题有一定效率要求。如果高精度代码比较慢,可能会超时。

习题5-16 医院设备利用(Use of Hospital Facilities, ACM/ICPC World Finals 1991, UVa212)

医院里有nn≤10)个手术室和mm≤30)个恢复室。每个病人首先会被分配到一个手术室,手术后会被分配到一个恢复室。从任意手术室到任意恢复室的时间均为t1,准备一个手术室和恢复室的时间分别为t2t3(一开始所有手术室和恢复室均准备好,只有接待完一个病人之后才需要为下一个病人准备)。

k名(k≤100)病人按照花名册顺序排队,T点钟准时开放手术室。每当有准备好的手术室时,队首病人进入其中编号最小的手术室。手术结束后,病人应立刻进入编号最小的恢复室。如果有多个病人同时结束手术,在编号较小的手术室做手术的病人优先进入编号较小的恢复室。输入保证病人无须排队等待恢复室。

输入nmTt1t2t3kk名病人的名字、手术时间和恢复时间,模拟这个过程。输入输出细节请参考原题。

提示:虽然是个模拟题,但是最好先理清思路,减少不必要的麻烦。本题是一个很好的编程练习,但难度也不小。

————————————————————

(1) C语言里连min函数都没有,可想而知还有多少常用的东西是无法直接用的。

(2) 不过流也可以加速,方法是关闭和stdio的同步,即调用ios::sync_with_stdio(false)。

(3) 在工程上不推荐这样做,不过因为算法竞赛的程序通常很小(多数不到200行),所以这样做也无大碍。

(4) 如果已完成了第3章的思考题,相信对此深有感触。

(5) 有些选手非常习惯这种思维方式,但是根据笔者的经验,也有很多选手非常不习惯这种思维方式。

(6) 具体有多慢?试试就知道了。请读者自行编写程序测试。

(7) 事实上,在C++中struct和class最主要的区别是默认访问权限和继承方式不同,而其他方面的差异很小。

(8) 有兴趣的读者可以研究一下C++的模板元编程(template metaprogramming)。在boost库中有很多模板元编程的优秀例子。

(9) 如果你想较真的话,这里有一个反例:经常使用git的程序员也有可能回答pull。

(10) 宏(macro)是一个很复杂的话题,这里读者暂时可以把带参数的宏理解为“类似于函数的东西”。

(11) 在C++中,重载了“( )”运算符的类或结构体叫做仿函数(functor)。

(12) 如果坚持需要更高的精度,可以采取多次随机的方法。

(13) 还有一个更通用的方法将在附录A中说明。

(14) 准确地说,应该是参数类型相同,参数的名字是无关紧要的。

(15) 注意vector并不是所有操作都快。例如vector提供了push_front操作,但由于在vector首部插入元素会引起所有元素往后移动,实际上push_front是很慢的。

(16) 任何一本C++语言教材都会介绍类继承,但它在算法竞赛中很少使用,所以这里略去细节。