4.07k likes | 4.29k Views
吉林 山东 浙江 全国奥林匹克信息学联赛辅导. 上海市控江中学 王建德. 2002 、 2003 年全国分区联赛复赛 情况分析. 题 型. 题 目. 与课内知识相关. 自由落体、级数求和、乒乓球、麦森数. 字符串处理. 字符近似查找. 贪心法. 均分纸牌、传染病控制. 回溯法. 选数、字串变换、栈、神经网络、侦探推理. 动态程序设计方法. 过河卒、数字游戏、加分二叉树. 几何计算. 矩形覆盖. 虽然 2002 、 2003 年全国奥林匹克信息学复赛中含许多可“一题多解” 的试题,但如果按照较优算法标准分类的话,大致可分为.
E N D
吉林 山东 浙江 全国奥林匹克信息学联赛辅导 上海市控江中学 王建德
2002、2003年全国分区联赛复赛 情况分析
题型 题 目 与课内知识相关 自由落体、级数求和、乒乓球、麦森数 字符串处理 字符近似查找 贪心法 均分纸牌、传染病控制 回溯法 选数、字串变换、栈、神经网络、侦探推理 动态程序设计方法 过河卒、数字游戏、加分二叉树 几何计算 矩形覆盖 虽然2002、2003年全国奥林匹克信息学复赛中含许多可“一题多解” 的试题,但如果按照较优算法标准分类的话,大致可分为
1、凸现信息学知识和学科知识整合的趋势。为了考核学生运用学科知识的能力,激发学生的创造力,2002年全国奥林匹克信息联赛(NOIP)中学科类的试题增加,并且首次出现了计算几何类的试题(矩形覆盖)。这说明信息学与学科的依赖关系日益凸现,学科基础好、尤其是数学素质好的人虽然不一定会编程,但希望学习编程的人愈来愈多;编程解题能力强的人势必有数学的潜质和爱好,他们中愈来愈多的人也希望深造数学。各门学科的交融和整合是奥林匹克信息学联赛活动发展的一个大趋势(有专家提议,数学教材讲算法,信息科技教材讲语言,上海的信息科技教材出现真值表(初中)和c语言(高中))。1、凸现信息学知识和学科知识整合的趋势。为了考核学生运用学科知识的能力,激发学生的创造力,2002年全国奥林匹克信息联赛(NOIP)中学科类的试题增加,并且首次出现了计算几何类的试题(矩形覆盖)。这说明信息学与学科的依赖关系日益凸现,学科基础好、尤其是数学素质好的人虽然不一定会编程,但希望学习编程的人愈来愈多;编程解题能力强的人势必有数学的潜质和爱好,他们中愈来愈多的人也希望深造数学。各门学科的交融和整合是奥林匹克信息学联赛活动发展的一个大趋势(有专家提议,数学教材讲算法,信息科技教材讲语言,上海的信息科技教材出现真值表(初中)和c语言(高中))。 2、“构造法” 或贪心策略类试题的引入,使得算法知识的不确定性和不稳定性增加。这正体现了科学的本质—知识是不断推陈出新的。 特 点
3、试题的综合性增加,并不一定随知识的分类而发生变化,有时几乎找不到一个单一的经典算法(字串变换——回溯法中有字符串处理),也找不到一个纯粹的数据结构问题(级数求和——需要为表达式的计算结果设计合适的数据类型),关键是你从哪个角度去分析,也就是说能不能综合所学的知识,应用自如地解决问题。选手的综合素质愈高,得胜的机率愈大; 4、经常面对着不知道算法的试题,面对着谁都不知如何处置的情境(经常出现许多选手在一题中得0分、优秀选手表现失常的情况),因此必须使学生正确地理解问题、深入问题的空间并形成解决问题的意识、习惯和能力。能不能创造性地应答没有遇到过的挑战,成为培训的基本要求和目标。
2、处理好前沿性与基础性、直线培训和散点培训、循序渐进与跳跃式的矛盾。如果恪守按部就班的培训程序,不谋求跳跃式学习,将离全国和国际奥林匹克信息学活动的前沿、离世界程序设计知识的前沿愈来愈远。因此在进行基础课程学习的同时,必须有追逐前沿的选择性学习。这里,有时候心理的障碍比科学上的障碍更难跨越,敢不敢的问题比能不能的问题更突出。其实在学习中或多或少地都有必要的跳跃,不少人还能够实现比较大的跳跃(爱笛生小学三年级退学、比尔.盖茨大学三年级退学)2、处理好前沿性与基础性、直线培训和散点培训、循序渐进与跳跃式的矛盾。如果恪守按部就班的培训程序,不谋求跳跃式学习,将离全国和国际奥林匹克信息学活动的前沿、离世界程序设计知识的前沿愈来愈远。因此在进行基础课程学习的同时,必须有追逐前沿的选择性学习。这里,有时候心理的障碍比科学上的障碍更难跨越,敢不敢的问题比能不能的问题更突出。其实在学习中或多或少地都有必要的跳跃,不少人还能够实现比较大的跳跃(爱笛生小学三年级退学、比尔.盖茨大学三年级退学) 1、培养问题意识和问题能力。创造始于问题。“有了问题才会思考,有了思考才有解决问题的方法,才有找到独立思路的可能(陶行知)”。有问题虽然不一定有创造,但没有问题一定没有创造(想一想当前的解法有没有缺陷,有没有更好的算法,它与哪些问题有联系,与哪些知识相关联,还可以拓延出哪些问题,要解决这些问题还需要哪些知识); 启 示
学生必须学会从浩如烟海的信息中选择最有价值的知识,构建个性化(符合自己能力结构和兴趣结构)和竞争需要的知识结构学生必须学会从浩如烟海的信息中选择最有价值的知识,构建个性化(符合自己能力结构和兴趣结构)和竞争需要的知识结构 • 培训内容要有选择性,因为除了出题者,谁也说不清楚在未来竞赛中究竟什么知识是必要的(对基础的理解是主观的选择。例如中国、美国和俄罗斯的理科教材大不相同,有的同年级同学科的教材相差三分之二),因此不可能把所有重要的东西都选择好了给学生,而是应该将直线培训与散点培训相结合,选择部分重要的东西交给学生,让他们自己去探索若干知识点之间的联系,补充自己认为需要补充的知识。 3、参与活动的学生应由竞争关系和独立关系(你做你的,我干我的,程序和算法互相保密,彼此津津乐道于对方的失败和自己的成功)转向合作学习的关系(通过研讨算法、集中编程、互测数据等互相合作的方式完成学习任务)
学生的心理调适: • 我掌握的知识仅不过是沧海一粟(进取心); • 固守错误的概念比一无所知更可怕(明智); • 三人之行必有我师(谦虚); • 知识生产社会化条件下人的基本素质之一是合作精神(现在的重大科学发明需要成百上千科学家进行长期甚至跨国的合作,例如制作windows,人类基因工程)(现代意识); • 合作学习的效应: • 集思广益容易出好的算法; • 群体设计的测试数据相对全面; • 在群体活动中能比较客观的反映自己能力情况; • 每个学生在付出与给予中可提高合作精神和编程能力,成功者往往是那些相容性好、 乐于帮助他人,并且善于取他人之长的学生(符文杰、张一飞等)。 前提条件:水平相当的同质成员或各有所长(包括数学知识、编程能力和思维方式等解题所需的各种因素)的异质成员是开展合作学习的组织基础;
4、选手面对从未遇到过的挑战应调整好心态,不要急功近利,要只管耕耘、不问收获、潜心钻研、其乐无穷。那怕是一两次失误,也是砥砺之石,可从中汲取有益的经验和教训。“不是一番寒彻骨,哪得梅花扑鼻香”。4、选手面对从未遇到过的挑战应调整好心态,不要急功近利,要只管耕耘、不问收获、潜心钻研、其乐无穷。那怕是一两次失误,也是砥砺之石,可从中汲取有益的经验和教训。“不是一番寒彻骨,哪得梅花扑鼻香”。
1、基础题 2、计算机基础知识 3、字符串运算 4、高精度运算 6、搜索算法 5、图的运算 8、构造算法 7、贪心法 9、动态程序设计
基础题 有些基础题虽然直接给出了计算公式或算法十分明显(例如统计数和),但是,如果变量的数据类型选错了,或者不会文件操作,同样会做错题,导致意外的失误。因此作题必须强调两基: 基础知识 基本基能
级数求和 已知:Sn=1+1/2+1/3+….+1/n。显然当n.非常大的时候,Sn可大于任何一个整数K。现给出一个整数K(1≤K≤15),要求计算出一个最小的n,使得Sn>K。输入键盘输入 k输出 屏幕输出 n输入输出样例输入: 1输出: 2
算法分析 • 该题考核选手的并不是编程能力,而是选择变量类型的能力。由于该数列是递减的,而k的上限为15,因此项数很大,即便是longint也容纳不下。但未必非高精度运算不可。只要启动浮点数运算({$n+}),将项数设为extended类型,便可以得出正确解。 • {$n+} {启动浮点数运算} • var • s,b,k:extended;{ 数列的和、项数、最接近sn(大于sn)的整数值} • begin • s←0; {数列的和初始化} • b←0; {项数初始化} • readln(k); {读最接近sn(大于sn)的整数值k} • while s<=k do {若目前数列的和小于k,则项数b+1,计算sb} • begin b←b+1; s←s+1/b; end;{while} • 输出项数round(b); • end.{main} 关键是s的类型。如果设为string,则增加了编程难度;如果设为除extended外的其它类型,则会失分!
乒乓球(Table.pas) 【问题背景】国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革,以推动乒乓球运动在全球的普及。其中11分制改革引起了很大的争议,有一部分球员因为无法适应新规则只能选择退役。华华就是其中一位,他退役之后走上了乒乓球研究工作,意图弄明白11分制和21分制对选手的不同影响。在开展他的研究之前,他首先需要对他多年比赛的统计数据进行一些分析,所以需要你的帮忙。 【问题描述】华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在11分制和21分制下,双方的比赛结果(截至记录末尾)。 比如现在有这么一份记录,(其中W表示华华获得一分,L表示华华对手获得一分):
WWWWWWWWWWWWWWWWWWWWWWLW 在11分制下,此时比赛的结果是华华第一局11比0获胜,第二局11比0获胜,正在进行第三局,当前比分1比1。而在21分制下,此时比赛结果是华华第一局21比0获胜,正在进行第二局,比分2比1。如果一局比赛刚开始,则此时比分为0比0。 你的程序就是要对于一系列比赛信息的输入(WL形式),输出正确的结果。 【输入格式】每个输入文件包含若干行字符串(每行至多20个字母),字符串有大写的W、L和E组成。其中E表示比赛信息结束,程序应该忽略E之后的所有内容。 【输出格式】输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。其中第一部分是11分制下的结果,第二部分是21分制下的结果,两部分之间由一个空行分隔。
算法分析 首先,对当前输入行计算11分制下每一局比赛的比分。设 a为当前局华华的得分,每输入一个‘W’,a+1;b为当前局对方的得分,每输入一个‘L’,b+1。若输入‘E’或者华华的得分a或者对方得分b达到11分且双方的分数差值大于1(((a≥11) or(b≥11))and (abs(a-b)>1)),则输出当前局的比分a:b。请注意,如果输入的字符为‘E’,则标志比赛结束,11分制计算完毕;否则,继续读下一个字符,计算新一局的比分。然后,对当前输入行计算21分制下每一局比赛的比分。计算方法基本如上。有所不同的是,若华华得分a或者对方得分b达到21分且双方的分数差值大于1(((a≥21) or(b≥21))and (abs(a-b)>1)),则输出当前局的比分a:b。 按照上述方法对每一输入行计算11分制和21分制的比赛结果,直至文件读完(eof(input))为止。
a:=0;b:=0;{新一局的比分初始化} end;{then} end; {'W',’L’} end;{case} end;{while} readln; end; {while} a:=0; b:=0; {新一局的比分初始化} writeln; reset(input);{重新读输入行} while not eof(input) do{若文件未读完且比赛未结束,则循环} begin while not eoln(input) do{若当前行处理完,则21分制的比赛结束} begin read(ch);{读一个字符}
case ch of{根据字符的种类分情形处理} ‘E’: begin{若比赛结束,则输出双方比分,退出21分制的计算过程} writeln(a,':',b); break; end;{ 'E'} 'W',’L’: begin{华华或对方得一分} if ch=’W’then inc(a) else inc(b); if ((a>=21)or(b>=21)) and (abs(a-b)>1) {若有一方得分达到21分且双方的分数差值大于1,则输出双方比分} then begin writeln(a,':',b); a:=0;b:=0;{新一局的比分初始化} end;{then} end;{ 'W',’L’} end;{case} end;{while} readln; end;{while} close(input); close(output);{关闭输入文件和输出文件} 关键是文件操作。如果在计算21分制的得分前,不会通过reset(input)将读头移到文件首,则会出错!
串运算 串是由零个或多个字符组成的有限序列。一个串中包含的字符个数称为这个串的长度。长度为零的串称为空串,它不包含任何字符。 在串运算中充分利用系统的库函数 1.连接运算——函数concat(s1,[,s2,…,sn]):其中值参s1,‥,sn为string类型,函数值为string类型。若连接后的串长大于255,则自动截断超出部分。 2.求子串——函数copy(s,i,l):其中值参s为string类型,i和l为integer类型。函数返回s串中第i个字符开始、长度为l的子串(string类型)。若i大于s的长度,则回送一个空串;若l大于第 i个字符开始的余串长度,则仅回送余串。 3.删子串——过程delete(var s,i,l):其中变量参数s为string类型,值参i、l为ingteger类型。该过程删去s中第i个字符开始的长度为l的子串,并返回剩余串s。若i大于原串s的长度,则不删任何字符;若l大于第i个字符开始的余串长度,则删去余串。 4.插入子串——过程insert(s1, var s,i):变量参数s为string类型,值参s1为string类型。该过程将s1子串插入空串s的第i个字符位置处,并返回插入后的结果s。若插入后s的串长大于255个字符,则截断超出部分。
5.求串长——函数length(s):值参s为string类型。该函数返回s串的实际长度值(integer类型)。5.求串长——函数length(s):值参s为string类型。该函数返回s串的实际长度值(integer类型)。 6.搜索子串位置——函数pos(s1,s2):值参s1和s2为string类型。若s1是s2的一个子串,则返回s1中第1个字符在s2串中的位置(integer类型);若s1非s2的一个子串,则返回0。 7.数值转换为数串——过程str(x,var s):值参x为integer类型或real类型,变量参数s为string类型。该过程将返回数值x对应的数串s。 8.数串转换为数值——过程val(s,var v,var c):值参s为string类型,变量参数v为integer类型或real类型,变量参数c为integer类型。该过程试将s串转换成数值v。若转换成功,则c为0,并返回对应的数值v;否则c为无效字符的序数。 9.字符的大写转换——函数upcase(ch):值参ch为char类型。该函数返回ch字符的大写体(char类型)
数码排序 设有n个正整数,将他们连接成一排,组成一个最大的多位整数.例如:n=3时,3个整数13,312,343,连成的最大整数为:34331213。又如:n=4时,4个整数7,13,4,246连接成的最大整数为7424613。 程序输入:N N个数 程序输出:连接成的多位数
由于连接后的字串长度不变,我们可以利用字符串的大小顺序和十进制的进位关系,对n个数串s进行排序由于连接后的字串长度不变,我们可以利用字符串的大小顺序和十进制的进位关系,对n个数串s进行排序 for i←1 to n-1 do {顺序排定s1…sn-1} for j←i+1 to n do{si依次与si+1…sn比较} if s[i]<s[j] then {若sj大于si,则si与sj交换位置} begin s1←s[i];s[i] ←s[j];s[j] ←s1; end;{then} for i←1 to n do write(s[i]);{输出最大的多位整数}
字符串编辑 从键盘输入一个字符串(长度≤40个字符),并以字符’.’结束. 例如:’This is a book.’,现对该字符串进行编辑,编辑功能有: D:删除一个字符,命令的方式为:D a。其中a为被删除的字符。例如:D s 表示删除字符’s’,若字符串中有多个’s’,则删除第一次出现的,如上例中删除的结果为’Thi is a book.’ I:插入一个字符,命令的格式为:I a1 a2。其中a1表示插入到指定字符前面,a2表示将要插入的字符。例如:I s d 表示在指定字符’s’的前面插入字符’d’,若原串中有多个’s’,则插入在最后一个字符的前面。如上例中,原串:’This is a book.’,插入后:’This ids a book.’ R:替换一个字符,命令格式为:R a1 a2。其中a1为被替换的字符,a2为替换的字符,若在原串中有多个a1,则应全部替换。例如:原串:’This is a book.’。 输入命令: R o e,替换后:’This is a beek.’。 在编辑过程中,若出现被指定的字符不存在时,则给出提示信息
设 s—原串和目标串; que—命令串。其中que[1]为命令字, que[2]和que[4]为空格。que[3]和que[5]的定义如下: 在D命令中,que[3]为被删字符; 在I命令中,que[3]为插入位置字符,que[5]为被插入字符; 在R命令中,que[3]为被替换字符, que[5]为替换字符;
删除操作 字串S中寻找que[3]。若找不到,则失败退出;否则,将该字符删去: i←pos(que[3],s); if i=0 then begin writeln(’Error!’);halt;end{then} else delete(s,i,1); 插入操作 从S串尾出发,由右而左寻找que[3]字符的位置。若找不到,失败退出;否则将que[5]字符插在该位置前i←length(s);{由右而左寻找que[3]字符的位置i} while (i>0)and(s[i] ≠que[3]) do i←i-1; if i≠0then insert(que[5],s,i){将que[5]字符插在i位置前} else begin writeln(’Error!’);halt;end;{else}
替换操作 从串首出发,由左而右寻找que[3]字符的位置。若找不到,失败退出;否则将que[5]替换所有的que[3]字符: error←true; for i←1 to length(s)do{由左而右替换que[3]字符} if s[i]=que[3] then begin error←false;s[i]←que[5]; end;{then} if error then{若找不到,失败退出} begin writeln(’Error!’);halt; end{then}
主程序 输入原串s和命令串que; case que[1] of ’D’:begin 删除操作;end;{ ’D’} ’I’:begin 插入操作;end;{ ’I’} ’R’:begin 替换操作;end;{ ’R’} end;{case} 输出s;
字符近似查找 设有n个单词的字典表(1≤n≤100)。计算某单词在字典表中的四种匹配情况(字典表中的单词和待匹配单词的长度上限为255): i:该单词在字典表中的序号; Ei:在字典表中仅有一个字符不匹配的单词序号; Fi:在字典表中多或少一个字符(其余字符匹配)的单词序号; N:其他情况 当查找时有多个单词符合条件,仅要求第一个单词的序号即可。 输入文件 输入文件名为a.in,文件的格式如下: n(字典表的单词数) n行,每行一个单词 待匹配单词
输出文件 输出文件名a.out,格式如下: i Ei Fi 其中i为字典表中符合条件的单词序号(1≤i≤n),若字典表中不存在符合条件的单词,则对应的i=0。若上述三种情况不存在,则输出N。 输入输出样例 输入1: 5 abcde abc asdfasfd abcd aacd abcd 输出1: 4 E5 F1 输入2: 1 a b 输出2: 0 E0 F0 N
我们将字典表中的单词分成3类: 第1类:单词与待匹配单词多或少一个字符,其余字符匹配; 第2类:单词仅有一个字符与待匹配单词不匹配; 第3类:单词与待匹配单词完全匹配; 设 const note :array[1..3] of string=('F','E',''); {匹配情况的标志} var want :string; {待匹配单词} list :array[1..100] of string; {字典表。其中list[i]为字典i} ans :array[1..100] of integer;{单词的类别序列。其中 ans[i]= }
1、匹配情况的计算 • ⑴计算两个等长字串中不同字符的个数 • function find(a,b:string):integer;{输入两个等长字串a,b,计算和返回不同字符的个数} • var i,tot:integer; • begin • tot←0; • for i←1 to length(a) do if a[i]<>b[i] then inc(tot); • find←tot; • end;{ find }
⑵判别一个字串是否比另一个字串多一个字符(其余字符匹配)⑵判别一个字串是否比另一个字串多一个字符(其余字符匹配) • 我们不知道长度大1的字串究竟在哪个位置上多出一个字符,无奈,只能将该字串的每一个字符试插在另一个字串的对应位置上。如果插入后使得两串相同,则说明猜想成立。否则猜想不成立。 • function check(a,b:string):integer;{输入字串a,b。若b能够在a的基础上添加一个字符得到的话,则返回1;否则返回0} • var i:integer; • begin • check←0; • for i←0 to length(a) do begin • a←copy(a,1,i)+b[i+1]+copy(a,i+1,255);{在a[i]后插入b[i+1]} • if a=b {若插入后两串相同,则成功退出} • then begin check←1;exit;end;{then} • delete(a,i+1,1); {删去a中的插入字符} • end;{for} • end;{check}
2、计算字典表中的每一类单词 • 首先,我们依次计算每一个单词的类别序号 • 在单词i与待匹配单词等长的情况下,若两串相同,则单词i的类别记为3;若两串仅有一个字符不同,则单词i的类别记为2; • 若单词i比待匹配单词多或少一个字符(其余字符匹配),则单词i的类别记为1;否则单词i的类别记为0; • 然后根据ans序列在字典表中依次搜索类别3‥类别1的单词,输出对应的单词序号。如果在字典表中不存在上述3种类别的单词,则输出‘N’。 • fillchar(ans,sizeof(ans),0); { 单词的类别序列初始化} • for i←1 to n do begin {对字典中的每个单词进行分类} • if length(list[i])=length(want) {若单词i与待匹配单词等长} • then begin • k←find(list[i],want);{计算单词i与待匹配单词的不同字符个数} • if k=0 then ans[i] ←3; {记下类别序号} • if k=1 then ans[i] ←2; • end;{then}
{若单词i比待匹配单词多或少一个字符(其余字符匹配),则单词i的类别记为1;否则单词i的类别记为0}{若单词i比待匹配单词多或少一个字符(其余字符匹配),则单词i的类别记为1;否则单词i的类别记为0} if length(list[i])+1=length(want) then ans[i] ←check(list[i],want); if length(list[i])=length(want)+1 then ans[i] ←check(want,list[i]); end;{for} have←false; {匹配情况存在的标志初始化} for i←3 downto 1 do begin {依次输出每一类别的单词在字典表最先出现的序号} k←0; for j←1 to n do if ans[j]=i then begin k←j;break;end;{then} have←have or (k>0); writeln(note[i],k); end;{for}
高精度的十进制运算一、数据类型 由于待处理的数据超过了任何一种数据类型所能容纳的范围,因此必须采用数串形式输入,并将其转化为整数数组。该数组的每一个元素对应一位十进制数,由其下标顺序指明位序号。运算规则如同算术运算。由于高精度运算的结果可能使得数据长度发生增减,因此除需要用整数数组存储数据外,还需要用一个整数变量记录整数数组的元素个数,即数据的实际长度。
Type numtype=array[1..500]of word;{整数数组类型} vara,b:numtype;{a和b为整数数组}la,lb:integer;{整数数组a的长度和b的长度} s:string;{输入数串} 将数串s转化为整数数组a的方法如下:k←length(s);for i←1 to k do a[k-i+1]←ord(s[i])-ord(‘0’);
1、加法运算a←a+b(a、b为numtype类型) 首先,确定a和b中的最大位数x(x=max{la,lb})。然后依照由低位至高位(第1位至第x位)的顺序进行加法运算。在每一次位运算中,a当前位加b当前位的和除以10,其整商即为进位,其余数即为和的当前位。在进行了x位的加法后,若最高位有进位(a[x+1]≠0),则a的长度为x+1。 procedure plus(var a:numtype;b:numtype);{a←a+b} var i,x:byte; begin if la≥lb then x←la {确定两数中的最大位数} else x←lb; for i←1 to x do {逐位相加} begin a[i] ←a[i]+b[i];a[i+1] ←a[i+1]+a[i] div 10;a[i] ←a[i] mod 10 end;{for} while a[x+1]≠0 do x←x+1; {和的最高位进位} la←x-1; end;{ plus }
求回文数 若一个数(首位不为零)从左向右读与从右向左读都是一样,我们就将其称之为回文数。例如:给定一个10进制数56,将56加65(即把56从右向左读),得到的121是一个回文数。又如,对于10进制数87: STEP1:87+78=165 STEP2:165+561=726 STEP3:726+627=1353 STEP4:1353+3531=4884 在这里的一步是指进行了一次N进制的加法,上例最少用了4步得到回文数4884。 写一个程序,给定一个N(2≤N≤10,N=16)进制数m,m的位数上限为20。求最少经过几步可以得到回文数。如果在30步以内(包括30步)不可能得到回文数,则输出“impossible” 样例: INPUT OUTPUT N=9 m=87 STEP=6
1.将数串s转化为整数数组m 设数串s=s1‥sp,串长为p。其中si为第p-i+1位n进制数(1≤i≤p)。我们将s转化为整数数组m=m[p]‥m[1],其中m[i]对应第i位n进制数。 type mtype=array[1..100]of integer; var m:mtype; 按下述方法将s转化为整数数组m: p←length(s);{计算s的串长} for i←1 to p do {从最高位开始计算整数数组m } begin k←p-i+1;{计算si对应于的m数组下标} case s[i] of{转换si} ’a’..’f’:m[k]←10+ord(s[i])-ord(’a’); ’0’..’9’:m[k]←ord(s[i])-ord(’0’); else 输出错误信息并退出程序; end;{case} end;{for}
2.判别整数数组m是否为回文数 function check (m: mtype) :boolean;{若整数数组m为回文数,则返回true,否则返回false} var i:integer; begin check←false; for i←1to do if m[i] ≠m[p-i+1] then exit;{返回m非回文数标志} check←true;{返回m为回文数标志} end;{check}
3.n进制加法运算 整数数组m1与其反序数m2进行n进制加法运算,得到结果m1 procedure solve(var m1: mtype); var m2: mtype; begin for i←1 to p do m2[i]←m1[p-i+1];{计算反序数m2} for i←1 to p do {由右而左逐位相加} begin m1[i]←m1[i]+m2[i];m1[i+1]← ;{进位} m1[i]←m1[i]mod n;{确定当前位} end;{for} if m1[p+1] ≠0 then p←p+1;{最高位进位} if check (m1)then 输出步数并退出程序; end;{solve}
4.主程序 输入进制数n和数串s; fillchar(m,sizeof(m),0) 将s转换为整数数组m; if check(m) then begin 输出步数0;halt;end;{then} 步数初始化为0; while 步数≤30 do begin 步数+1;solve(m);end;{while} 输出无解信息;
2、减法运算a←a-b(a、b为numtype类型,a>b) 依照由低位至高位(第1位至第la位)的顺序进行减法运算。在每一次位运算中,若出现不够减的情况(a[i]<b[i]),则向高位借位(dec(a[i+1]),a[i]←a[i]+10)。在进行了la位的减法后,若最高位为零(a[la]=0),则a的长度-1。 procedure minus(var a:atype;var la:integer;b:atype);{计算a=a-b,返回差a及其长度la} var i:integer; begin for i←1 to la do {逐位相减} begin if a[i]<b[i]{借位} then begin dec(a[i+1]);a[i]←a[i]+10;end;{then} a[i]←a[i]-b[i];{计算差的第i位} end;{for} while a[la]=0 do dec(la);{计算差的实际长度} end;{minus}
3、乘法运算a←a*c(a为numtype类型,c为字节型) 按照乘法规则,从a的第1位开始逐位与c相乘。在第i位乘法运算中(1≤i≤la),a的i位与c的乘积必须加上i-1位的进位(i-1位的乘积除以10的整商),然后规整积的i-1位(取i-1位的乘积对10的余数): procedure multiply(var a:numtype;c:byte); var i:byte; begin a[1] ←a[1]*c;{第1位初始化} for i←2 to la do{逐位相乘} begin a[i] ←a[i]*c;a[i] ←a[i]+a[i-1] div 10;a[i-1]←a[i-1] mod 10 end;{for} while a[la] ≥10 do{积的最高位进位} begin la←la+1;a[la] ←a[la-1] div 10;a[la-1] ←a[la-1] mod 10 end{while} end;{ multiply }
4、高精度除法 设a,b为除数和被除数,q和r为小数和余数序列,其中r[0]=0。按照除法规则逐位相除:若当前位hv的小数和余数与先前得出的某位i相同((r[i]=r[hv])and (q[i]=q[hv])),则说明除法成功。若i=0,则q[1]..q[hv]为小数;否则q[i]..q[hv-1]为循环节。上述过程一直进行到小数50位为止。 readln(f,a,b);{输入除数和被除数} if b=0 then begin writeln(f0,'Meaningless.'); continue end; if a*b<0{计算和输出商的整数部分} then begin write('-'); a:=abs(a); b:=abs(b) end; write(f0,a div b); if a mod b=0 then exit; a:=a mod b; write(‘.’);{开始计算小数部分} hv:=0; bn:=false;{小数位数和循环节标志初始化} r[0]:=0;{第0位余数为0,除尽的比较标志}
while hv<50 do begin{计算小数和余数的当前位} inc(hv); q[hv]:=a*10 div b;r[hv]:=a*10 mod b;a:=r[hv]; i:=0;{在小数和余数中寻找与当前位相同的位数i} while (i<=hv-1) and ((r[i]<>r[hv]) or (q[i]<>q[hv])) do inc(i); if i<=hv-1{若相同位存在或被除尽} then begin for j:=1 to i-1 do write(q[j]);{输出循环节前的小数} if i<>0 then write(f0,‘{’);{输出循环节} if i>0 then for j:=i to hv-1 do write(q[j]) else for j:=1 to hv do write(q[j]);{若第hv位小数和余数为0,则输出小数部分} if i<>0 then write('}');bn:=true; break{成功标志} end end; if not bn then for i:=1 to 50 do write(q[i]);{输出50位小数}
麦森数(Mason.pas) 【问题描述】形如2P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2P-1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。 任务:从文件中输入P(1000<P<3100000),计算2P-1的位数和最后500位数字(用十进制高精度数表示) 【输入格式】文件中只包含一个整数P(1000<P<3100000) 【输出格式】第一行:十进制高精度数2P-1的位数;第2-11行:十进制高精度数2P-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0) 不必验证2P-1与P是否为素数。
算法分析 显然,2P-1的位数为 ,计算和输出2P-1的最后500位数字,需要采用高精度运算。设 ans为2p-1对应的高精度数组; 我们将p转换为对应的二进制数Dn…D0,其中Di的权为2i。 = =( )2。将p对应二进制数中值为1的权2i作为2的次幂,组成ans=2P-1的一项( ),显然,后一项为前一项的平方。当前项存储在高精度数组I中,取后500位。 p= ans=2p-1= -1= -1。 我们将p对应二进制数中值为1的每一项连乘起来,每一次的乘积取后500位,最后的乘积ans-1即为2p-1对应的高精度数组。
计算ans←ans*l fillchar(ans1,sizeof(ans1),0);{乘积初始化} for i:=0 to 499 do{连乘当前项} for j:=0 to 499-i do inc(ans1[i+j],ans[i]*l[j]); for i:=0 to 498 do{对乘积进行规整} begin inc(ans1[i+1],ans1[i] div 10);ans1[i]:=ans1[i] mod 10; end;{for} ans1[499]:=ans1[499] mod 10; ans:=ans1;
计算l←l2 fillchar(l1,sizeof(l1),0);{高一位的当前项初始化} for i:=0 to 499 do{前一项的平方作为当前项,取后500位} for j:=0 to 499-i do inc(l1[i+j],l[i]*l[j]); for i:=0 to 498 do{对当前项进行规整} begin inc(l1[i+1],l1[i] div 10); l1[i]:=l1[i] mod 10; end;{for} l1[499]:=l1[499] mod 10; l:=l1;