《趣题学算法》—第1章1.2节简单的数学计算

1.2 简单的数学计算
以上那样利用循环重复将部分数据简单地累加,可以解决很多计数问题。然而,如果计数问题可以通过数学计算直接得出结果,往往可以大大改善算法的时间效率,请看下列问题。

问题1-5 小小度刷礼品
图片 7

问题描述

一年一度的百度之星大赛又开始了,这次参赛人数创下了吉尼斯世界纪录。于是,百度之星决定奖励一部分人:所有资格赛提交ID以x结尾的参赛选手将得到精美礼品一份。

小小度同学非常想得到这份礼品,于是他就连续提交了很多次,提交的ID从a连续到b。他想知道能得到多少份礼品,你能帮帮他吗?

输入

第一行一个正整数T表示测试案例数。

接下来T行,每行3个不含多余前置零的整数x,a,b(0leqslantxleqslant1018,1leqslantaleqslantbleqslant1018)。

输出

T行。每行为对应的数据下,小小度得到的礼品数。

输入样例

2
88888 88888 88888
36 237 893

输出样例

1
6

解题思路

(1)数据的输入与输出

题面中告诉我们,输入文件的第一个数据指出了所含的测试案例数T,每个案例的输入数据仅占一行,其中包含了3个分别表示ID尾数x、ID取值下界a和上界b的整数。计算所得结果为a~b内能够得到礼物的ID(尾数为x)个数,作为一行输出到文件中。表示成伪代码如下。

1 打开输入文件inputdata
2 创建输出文件outputdata
3 从inputdata中读取案例数T
4 for t←1 to T
5    do 从inputdata中读取案例数据x, a, b
6       result← GIFT(x, a, b)
7       将result作为一行写入outputdata中
8 关闭inputdata
9 关闭outpudata

其中,第6行调用过程GIFT (x, a, b)计算能够得到的礼物数是解决一个案例的关键。

(2)处理一个案例的算法过程

对一个测试案例x, a, b而言,很容易想到用列举法穷尽a~b所有的整数,检测每一个的尾数是否与x相等。跟踪相等的个数:

GIFT (x, a, b)
1 t← x的10进制位数
2 m←10t
3 count ←0
4 for i ←a to b
5    do if i Mod m=x
6          then count ←count+1
7 return count

算法1-5 对一个测试案例数据x, a, b,累加计算获得礼品数的算法过程

算法1-5中的第1、2行计算m=10t,其中t为x的10进制位数,可以用如下操作实现:

1 m←1
2 while m<x
3   do m←m*10

显然耗时为log10x,若a~b之间有n个数,则上述算法中第3~6行代码的运行时间是Θ(n),于是算法1-5的运行时间为Θ(log10x)+ Θ(n)。借助数学计算,我们可以把解决这个问题的算法时间缩小为Θ(log10x)。设x的10进制位数为t,令m为10t。例如,若x=36,a=237,b=893,则t=2,m=100。设ar =a Mod m,aq=a/m 。即ar为a除以m的余数,aq为a除以m的商。相仿地,设br=b Mod m,bq=b/m。对于x=36,a=237,b=893,有ar =37,aq=2,br=93,bq=8。由于ar =37>36=x,所以a(=237)之后最小的位数为x(=36)的数应为336。而由于br=93>36=x,故在b=893之前最大的尾数为36的数为836。因此,介于a,b之间尾数为x的数为336,436,536,636,736,836,一共有bq− (aq+1)+1=6个。

上例说明,对于x, a, b,若aleqslantxleqslantb,计算出ar =a Mod m,aq=a/m ,br=b Mod m,bq=b/m后,检测ar是否大于x。若是,则aq增1。相仿地,检测br是否小于x,若是,则bq减1。bq−aq+1即为所求。写成伪代码过程如下。

GET-GIFT(x, a, b)
1 t← x的10进制位数
2 m←10t
3 ar ←a Mod m,aq←a/m
4 br ←b Mod m,bq←b/m
5 if ar>x
6     then aq←aq+1
7 if br<x
8     then bq←bq-1
9 return bq-aq+1

算法1-6 对一个测试案例数据x, a, b,直接计算获得礼品数的算法过程

算法1-6中的第1~2行与算法1-5中的一样,耗时Θ(log10x)。而算法其余部分耗时为Θ(1)。于是,算法1-6的运行时间为Θ(log10x)。

解决本问题的算法的C++实现代码存储于文件夹laboratory/Get Gift中,读者可打开文件getgift.cpp研读,并试运行之。

问题描述
农夫John养了一群牛妞。有些牛妞很任性,时常离家出走。一天,John 得知了他的一头在外流浪的牛妞出后想立刻去把她领回家。John从数轴上的点N (0leqslantNleqslant100 000)处出发,牛妞出没于同一数轴上点K (0leqslantKleqslant100 000)处。John有两种移动方式:走路或远距飞跃。

走路:John在一分钟内可从点X走到点X-1或点X+1处。
远距飞跃:John可以在一分钟内从任意点X处飞跃到点2X处。
假定牛妞对自身的危险一无所知,一直在原地溜达,John最少要花多少时间才能够抓到她?

输入

输入文件的第一行仅含一个表示测试案例个数的整数T。其后跟着T行数据,每行数据描述一个测试案例,包括两个用空格隔开的整数:N和K。

输出

每个案例只有一行输出:John抓到牛妞的最少时间(分钟)。

输入样例

2
5 17
3 21

输出样例

4
6

解题思路

(1)数据的输入与输出

根据题面中对输入、输出数据的格式描述,我们可以将处理所有案例的过程表示如下。

1 打开输入文件inputdata
2 创建输出文件outputdata
3 从inputdata中读取案例数T
4 for t←1 to T
5    do 从inputdata中读取案例数据N, K
6       result←CATCH-THAT-COW(N, K)
7       将result作为一行写入outputdata中
8 关闭inputdata
9 关闭outpudata

其中,第6行调用过程CATCH-THAT-COW(N, K)计算John从N出发抓到位于K的牛妞要用的最短时间是解决一个案例的关键。

(2)处理一个案例的算法过程

John要想最快地从点N到达点K处,就要充分利用他的飞跃能力。需要注意的是,John步行时可来回走,而飞越只能是单向的。所以,当NgeqslantK时,John 只能从N步行到K(见图1-2(a))。此时,需要走N-K分钟。

考虑NK(见图1-2(b))。最理想的情况是N2q=K, 此时q即为所求。否则John有两个可能的走法:

① 从N飞跃q 次到达N2q,再往前走K−N2q分钟到达K,即用时为q+K−N2q分钟。

② 从N飞跃q+1次到达N2q+1,再往回走N2q+1−K分钟到达K,即用时为q+1+ N2q+1−K分钟。

取两者较小者可能是最优解。然而这并不全面,因为还有更加微妙的情况会出现。以题面的输入样例N=5,K=17为例。2×5<17,且22×5>17。按第①种方案,需用1+7=8分钟,而用第(2)种方案需用2+3=5分钟。但是,如果John先从N=5往回走1分钟,来到4(=22)处,然后从22处飞跃2次来到24(=16)处,再从24向前走1分钟就可到达K=17,这样所用的时间为4分钟,更短。

形式化地说,设不超过N的2的幂之最大者为2t,而不超过K的2的幂之最大者为2p。于是必有2tleqslantN<2t+1及2pleqslantK<2p+1(见图1(c))。此时,John 可以有4种不同的走法:

① 从N步行到2t,从2t飞跃到2p,再从2p走到K。用时:(N−2t)+(p−t)+(K−2p)。

② 从N步行到2t,从2t飞跃到2p+1,再从2p+1走到K。用时:(N−2t)+(p−t+1)+(2p+1−K)。

③ 从N步行到2t+1,从2t+1飞跃到2p,再从2p走到K。用时:(2t+1−N)+(p−t+1)+(K−2p)。

④ 从N步行到2t+1,从2t+1飞跃到2p+1,再从2p+1走到K。用时:(2t+1−N)+(p−t)+(2p+1− K)。

所以,若令a=q+K−2qN,b=q+1+2q+1N−K,c=(N−2t)+(p−t)+(K−2p),d=(N−2t)+(p−t+1)+(2p+1−K),e=(2t+1−N)+(p−t+1)+(K−2p),f=(2t+1−N)+(p−t)+(2p+1−K),则min{a, b, c, d, e, f}即为所求。上述算法可写成如下的伪代码。

CATCH-THAT-COW(N, K)
1 if N\geqslantK
2    then return N-K
3 p←max{i|2i\leqslantK}, q←max{i|2iN\leqslantK}, t←max{i|2i\leqslantN}
4 if N2q=K
5     then return q
6 a← q+K-N2q , b← q+1+ N2q+1-K,  c←(N-2t)+(p-t)+(K-2p)
7 d←(N-2t)+(p-t+1)+(2p+1-K), e←(2t+1-N)+(p-t+1)+(K-2p), f ←(2t+1-N)+(p-t)+(2p+1-K)
8 return min{a, b, c, d, e, f}

算法1-7 计算农夫John 从N到K最少时间的算法过程

算法1-7中,第3行计算不超过某整数x的最大的2的整幂指数,可以通过如下过程进行。

CALCULATE(x)
1 p←1, t←0
2 while p<x
3    do p←p*2
4      t←t+1
5 if p>x
6    then t←t-1
7 return t and 2t

算法1-8 计算不超过最大的2的整幂指数的整数x的算法过程

由于tleqslantlgx,而第2~4行的while循环每次重复t(从0开始)都会增加1,所以该循环至多重复lgx次。于是算法1-8的运行时间为Θ(lgx)。算法1-7的第3行调用三次CALCULATE过程就可完成指数p,2p,q,N2q,t,2t的计算,而算法的其他部分均在常数时间内完成,所以算法1-7的运行时间为O(lgK)。

解决本问题的算法的C++实现代码存储于文件夹laboratory/Catch The Cow中,读者可打开文件Catch The Cow.cpp研读,并试运行之。

有些问题需要综合地使用数学计算和累加和方法加以解决。

描述
你是个暴脾气,最讨厌等待。你打算去新奥尔良拜访一位朋友。来到公交站你才发现这里的调度表是世界上最糟糕的。这个车站并没有列出各路公交车班车到达与出发的时间表,只列出各相邻班车的发车间隔时长。暴躁的你从包中抓出平板电脑,试图写一段程序以计算最近来到的班车还需要等待多久。嘿,看来你只能这样了,不是吗?

输入

本问题的输入包含不超过100个测试案例。每个案例的输入数据格式如下。

一个测试案例的数据包括四个部分:

开头行——只有一行,“START N”,其中的N表示公交车路数(1leqslantNleqslant20)。

路线发车间隔区间行——共有N行。每行由M(1leqslantMleqslant10)个发车间隔时长组成,这些数据表示这一路线的各班车上一班发车起到本班车出发时刻的间隔时间长度。每个间隔时长是一个介于1~1000之间的整数。

到达时间——仅一行。该行数据表示你到达车站开始等待的时间。这个数据表示的是从当天车站开始运行到你来到车站的时间单位数(所有的线路的车都是从时间0开始运行的)。这是一个非负整数(若为0,意味着班车在你到站时起步)。

结束行——单一的一行,“END”。

最后一个测试案例后有一行“ENDOFINPUT”,作为输入结束标志。

输出

对每一个测试案例,恰有一行输出。这一行仅包含一个表示你在下一趟班车到来之前需要等待的时间单位数。我们希望你等来的这班车是去往新奥尔良的!

注意

每班公交连续不断地循环运行于它的线路上。

若乘客在班车离开时刻到达,他/她将搭上这班车。

输入样例

START 3
100 200 300
400 500 600
700 800 900
1000
END
START 3
100 200 300 4 3 2 4 2 22
800
10 1000
32767
END
ENDOFINPUT

输出样例

200
20

解题思路

(1)数据的输入与输出

按题面对输入文件的格式描述,输入包含多个测试案例,每个案例的第一行包含两个部分:开头标志“START”和表示本案例中公交车的路数N。案例数据接下来的N行数据表示每一路车各相邻班车的发车间隔。接着一行仅含一个表示乘客到达时间的整数。最后一行是案例结束标志“END”。例如,输入样例中第一个案例的第1行数据START 3表示该案例有3路公交车。后面3行数据表示各路公交的各班车的发车间隔,例如第1行的数据100 200 300表示1路车的第1、2班车的发车间隔为100,第2、3班车的发车间隔为200,第3、1班车的发车间隔为300。以此类推。最后一行数据1000表示乘客到来的时刻。

文件的结束标志是一行“ENDOFINPUT”。

依次读取每个案例的数据,将发车间隔数据组织成一个表durations,到达时间记为arrival,对案例数据进行处理,计算得到乘客最小等待时间,并将结果作为一行写入输出文件。描述成伪代码如下:

1 打开输入文件inputdata
2 创建输出文件outputdata
3 从inputdata中读取一行到s
4 while s≠"ENDOFINPUT"
5   do 略过s中的"START",并读取N
6      创建数组durations[1..N]
7      for i←1 to N
8          do从inputdata中读取一行到s
9            将s中的每一个整数数据添加到durations[i]中
10      从inputdata中读取arrival
11      result←WORLD-WORST-BUS-SCHEDULE(durations, arrival)
12      将result作为一行写入outputdata
13      在inputdata中略过一行"END"
14      从inputdata中读取一行到s
15 关闭inputdata
16 关闭outpudata

其中,第11行调用过程WORLD-WORST-BUS-SCHEDULE(durations, arrival)计算乘客最小的等车时间,是解决一个案例的关键。

(2)处理一个案例的算法过程

对一个案例而言,将各路车的出发间隔时长记录在一组数组durations中,durationsi表示第i路车第j次发车距第j-1次发车的时间间隔。Ti=sumnolimits_{j=1}^{{{m}_{i}}}{durationsi}表示第i路各班车运行一个循环所用的时间(i=1, 2,…,n)。若设本案例中乘客的到达时间为arrival,则Ri=arrival MOD Ti表示乘客来到车站时,第i路车运行完若干个循环周期后处于最新运行周期内的时间。例如,输入样例中的案例1中1路车3班车的一个运行周期T1为100+200+ 300=600,乘客到达时间arrival=1000,R1=arrival MOD T1=1000 MOD 600≡400。这意味着1路车的3班车已经运行了一个循环后400时乘客到达车站。于是,乘客需要等待的应该是当前周期内从开始到最近发车时间超过400的那班车。等待的时间自然就是从第一个使得差sumnolimits_{j=1}^{{{m}_{i}}}{durationsi}−Rigeqslant0(1leqslantkleqslantmi)的值。本例中此值为(100+200+300) −400 = 600 – 400=200。所有n路的等待时间中的最小值即为所求。以上算法思想写成伪代码过程如下。

WORLD-WORST-BUS-SCHEDULE(durations, arrival)
1 n←length[durations]
2 for i←1 to n
3     do mi←length[durations[i]]
4      Ti←durations[i][j]
5      Ri← arrival MOD Ti
6      k←1
7      while durations[i][j]-Ri <0
8        do k←k+1
9      time[i] ←durations[i][j]-Ri
10 return min(time)

算法1-9 计算乘客最小等待时间的算法过程

设案例中有n路公交,其中班次最多的班数为m。算法的运行时间取决于第2~9行的两层嵌套循环重复次数。外层for循环重复n次,里层的第4行实际上也是一个循环(计算累加和),重复次数最多为m。同样,第7~8行的while循环也至多重复m次。这两个内层的循环是并列的,所以运行时间为O(nm)。

解决本问题的算法的C++实现代码存储于文件夹laboratory/ World's Worst Bus Schedule中,读者可打开文件World's Worst Bus Schedule.cpp研读,并试运行之。

时间: 2024-11-10 00:32:50

《趣题学算法》—第1章1.2节简单的数学计算的相关文章

《趣题学算法》—第1章1.1节累积计数法

第1章 计数问题 趣题学算法 1.1 累积计数法 1.2 简单的数学计算 1.3 加法原理和乘法原理 1.4 图的性质 1.5 置换与轮换 人类的智力启蒙发端于计数.原始人在狩猎过程中为计数猎获物,手指.结绳等都是曾经使用过的计数工具.今天,我们所面对.思考的问题更加复杂.庞大,计数的任务需要强大的计算机来帮助我们完成.事实上,很多计算问题本身就是计数问题. 1.1 累积计数法 这样的问题在实际中往往要通过几个步骤来解决,每个步骤都会产生部分数据,问题的目标是计算出所有步骤产生数据的总和.对这样

《趣题学算法》—第0章0.1节App程序与算法

第0章 从这里开始趣题学算法0.1 App程序与算法 0.2 计算问题 0.3 算法的伪代码描述 0.4 算法的正确性 0.5 算法分析 0.6 算法运行时间的渐近表示 0.7 算法的程序实现 0.8 从这里开始 0.1 App程序与算法信息时代,人们时刻都在利用各种App解决生活.工作中的问题,或获取各种服务.早晨,手机里设定的闹钟铃声(或你喜欢的音乐)将你唤醒.来到餐厅,你用手中的IC卡到取餐处的刷卡机上支付美味早餐的费用.上班途中,打开手机上的音乐播放器,用美妙的乐声,打发掉挤在公交车上的

《趣题学算法》目录—导读

版权 趣题学算法 • 著 徐子珊 责任编辑 张 涛 • 人民邮电出版社出版发行 北京市丰台区成寿寺路11号 邮编 100164 电子邮件 315@ptpress.com.cn 网址 http://www.ptpress.com.cn • 读者服务热线:(010)81055410 反盗版热线:(010)81055315 内容提要 趣题学算法 本书共分10章.第0章讲解了算法的概念及体例说明.第1-7章分别就计数问题.信息查找问题.组合优化问题.图中搜索问题和数论问题展开,讨论了算法的构思和设计,详

《趣题学算法》—第0章0.8节从这里开始

0.8 从这里开始作为本书讨论的起点,本章通过解决一个典型的计算问题"计算逆序数",明确了诸如算法.伪代码.算法分析.C++程序等概念.术语或名称.通过讨论问题"移动电话"给出了本书每个问题讨论的体例:描述问题--理解问题--设计算法--分析算法的效率. 如果你是一位编程初学者,在看了这两个例子后是否会有这样的问题:怎么会想到这样解这些问题?其实,这和你在学校里学习数学时解应用题很相像.首先,看看题目是归类于代数.几何还是微积分?如果是代数题,再看是用解方程方法还是

《趣题学算法》—第0章0.3节算法的伪代码描述

0.3 算法的伪代码描述 上一节最后一段使用自然语言(汉语)描述了解决"计算逆序数"问题的算法.即如何将输入数据转换为输出数据的过程.在需要解决的问题很简单的情况下(例如"计算逆序数"问题),用自然语言描述解决这个问题的算法是不错的选择.但是,自然语言有一个重要特色--语义二岐性.语义二岐性在文学艺术方面有着非凡的作用:正话反说.双关语--.由此引起的误会.感情冲突--带给我们多少故事.小说.戏剧--.但是,在算法描述方面,语义二岐性却是我们必须避免的.因为,如果对

《趣题学算法》—第1章1.3节加法原理和乘法原理

1.3 加法原理和乘法原理 组合数学中有两条著名的原理--加法原理和乘法原理.利用这两条原理可以快速地解决一些计数问题. 加法原理:做一件事,完成它可以有n类办法,在第一类办法中有m1种不同的方法,在第二类办法中有m2种不同的方法,--,在第n类办法中有mn种不同的方法,那么完成这件事共有N=m1+m2+m3+-+mn种不同方法. 乘法原理:做一件事,完成它需要分成n个步骤,做第一步有m1种不同的方法,做第二步有m2种不同的方法,--,做第n步有mn种不同的方法,那么完成这件事共有N=m1×m2

《趣题学算法》—第0章0.2节计算问题

0.2 计算问题上面已经说到什么是计算问题,下面就来看一个有趣的计算问题. 问题描述这个学期Amy开始学习一门重要课程--线性代数.学到行列式的时候,每次遇到对给定的序列计算其逆序数,她都觉得是个很闹心的事.所以,她央求她的好朋友Ray为她写一段程序,用来解决这样的问题.作为回报,她答应在周末舞会上让Ray成为她的伦巴舞舞伴.所谓序列A的逆序数,指的是序列中满足iA[j]的所有二元组的个数.输入输入文件包含若干个测试案例.每个案例的第一行仅含一个表示序列中元素个数的整数N(1leqslantNl

《趣题学算法》—第0章0.7节算法的程序实现

0.7 算法的程序实现 有了解决问题的正确算法,就可以利用一种计算机程序设计语言将算法实现为可在计算机上运行的程序.本书选用业界使用最广泛.最成熟的C++语言来实现解决每一个问题的算法.C++语言是面向对象的程序设计语言,它为程序员提供了一个庞大的标准库.有趣的是,C++脱胎于C语言.所以,读者若具有C语言某种程度的训练,对于理解本书提供的C++代码一定是大有裨益的.闲话少说,让我们先来一睹C++语言程序的"芳容"吧. 解决问题0-1"计算逆序数"的C++程序如下.

《趣题学算法》—第1章1.4节图的性质

1.4 图的性质 有的计数问题所涉及的事物间存在着某种关系,这样的问题往往可以表示成一个图(Graph):问题中的每个事物视为一个顶点,两个顶点之间如果存在这关系,就在这两个顶点之间做一条称为边的弧.形式化描述为由问题中的各事物构成的集合,记为顶点集V={v1,v2,-,vn},边集E={(vi, vj)| vi, vj ∈V且vi和vj具有关系}. 例如,图1-3将五个人Adward.John.Philips.Robin和Smith之间的朋友关系表示成了一个图.其中,Adward与Robin和