资源描述
读程序写结果之基础篇
读程序写结果,大致可以考察学生几方面的能力:一是程序设计语言的掌握情况;二是相关算法的掌握情况;三是数学的知识面及运算能力;四是细心、耐心的心理品质。在NOIP初赛中所占的分值,近几年一直维持在(4×8)分。对于参赛选手,又快又准地完成这类题,显得尤为重要。本系列文章将全面分析这类题的常用解题方法与技巧,敬请期待。
本讲,我们简要说明一下阅读程序写结果,或者说参与NOIP初赛,要了解并掌握的一些语言基础(以Pascal语言为例),以及解决此类型题目的最基本解法。
一、 Pascal相关知识备忘(以free Pascal 2.04为语言载体)
熟练掌握并灵活使用以下Pascal语言相关知识:
(一)、常用运算:
1、算术运算:+、-、*、/、DIV、MOD
2、字符串运算:+ (字符串连接)
3、集合运算:+(并集)、*(交集)、一(差集)、in
2、关系运算:>、<、=、<>、>=、<=
3、逻辑运算:NOT、AND、OR、XOR
(二)、常用子程序
1、 求绝对值函数abs(x) 如:abs(3)返回值为3;abs(-3.1)返回值为:3.1
2、 取整函数int(x) 定义:function Int(X: Real): Real;
如 int(3.6)返回值为:3.0;int(-3.6)返回值为:-3.0
3、 截尾函数trunc(x) 定义:function Trunc(X: Real): Longint
如 trunc(3.6)返回值为:3;trunc(-3.6) 返回值为:-3
4、 四舍五入函数round(x)
如 R := round(123.456);{123} R := round(12.56);{13}
R := round(-123.456);{-123} R := round(-12.56);{-13}
5、 取小数函数frac(x)
如R := Frac(123.456); { 0.456 }; R := Frac(-123.456); { -0.456 }
6、 求平方根函数sqrt(x)和平方函数sqr(x)
如:R := sqrt(4); { 2.0 }; R := sqr(4); { 16 }
7、 求以e为底幂函数exp(x) :
8、 求以e为底对数函数ln(x) :
9、 随机数函数function random[(range:word)]:<same type>
randomize 随机数初始化语句
random 返回 之间的随机实数
random(range) 返回随机整数
10、 求字符x对应序号函数ord(x) 如R := ord(‘A’); { 65 }
11、 求序号x对应字符函数chr(x) 如R := chr(65); { ‘A’ }
12、 将字符串小写转换为大写函数upcase(st) 如R := upcase(‘AbcD’); { ‘ABCD’ }
13、 求前趋函数pred(x) 如R := pred(‘B’); { ‘A’ }
14、 求后继函数succ(x) 如R := succ(‘B’); { ‘C’ }
15、 判断x是否为奇数函数odd(x) 如R := odd(7); { TRUE }; 如R := odd(8); { FALSE }
16、 字符转换为数值过程val(str,a,b)
如,执行语句val(‘2.4’,a,b); 后,a值为:2.4
执行语句val(‘2c4’,a,b); 后,a为:0,b为:2
17、 数值转换为字符过程Str(a,st)
如,执行语句str(12, st); 后,st值为:’12’
18、 求字串st长度函数length(st) 如R := length(‘ABC’); { 3 }
19、 函数Pos(st1,st):查找st1在st里的起始位置,整型。
如R := pos(‘cd’,’abcde”); {3}
20、 函数Copy(st,a,b):提取st里第a个位置的b个字符。
如R := copy(‘abcdef’,2,3); { ‘bcd’ }
21、 过程Delete(st,a,b):删除st中第a个位置的b个字符
如,执行语句:
st=’abcdef’; delete(st,2,3);
后,st值为:’aef’
22、 过程Insert(st1,st,a):把st1插入st的第a个位置中
如,执行语句:
st=’abcdef’; insert(‘xy’,st,3);
后,st值为:’abxycdef’
23、 过程Fillchar(x,y,a) :按字节填充。常用Fillchar(a,sizeof(a),0)对数组的所有元素进行清零。
24、 过程Inc(i) 使i:=i+1;
Inc(I,b) 使I:=I+b;
25、 过程dec(i) 使i:=i-1;
dec(I,b) 使I:=I-b;
26、 EOF:判断当前打开的文件是否已到文件尾
27、 EOLN:判断是否为行尾
(三)、位运算
1、 SHR:x SHR n 把x换成二进制后向右移n位
2、 SHL:x SHL n 把x换成二进制后向左移n位
3、 and :位与。(1可视为真;0为假)。在其它领域可用符号∧、·表示。
4、 or :位或。在其它领域可用符号∨、+表示
5、 xor:位异或。在其它领域可用符号表示
6、 not:按位取反。在其它领域可用符号表示
例:Var i,j:byte;
begin
i:=10; { (10)10= (0000 1010)2 }
j:=12; { (12)10= (0000 1100)2 }
writeln(i shr 2); {(0000 1010)2向右移两位,高位补零:(0000 0010)2,输出2}
writeln(i shl 2); {(0000 1010)2向左移两位,低位补零:(0010 1000)2,输出40}
writeln(i and j); { (0000 1010)2 ∧ (0000 1100)2=(0000 1000)2 ,输出8}
writeln(i or j); { (0000 1010)2 ∨ (0000 1100)2=(0000 1110)2,输出14}
writeln(i xor j); { (0000 1010)2 (0000 1100)2=(0000110)2 ,输出6}
writeln(not i); { (0000 1010)2 =(1111 0101)2,输出245}
end.
(四)、几个语句及几个符号
1、 break:退出循环
2、 continue:直接回到循环体顶部执行
3、 exit:退出当前子程序。若是主程序,结束运行。
4、 halt:结束运行,回到操作系统
5、 记录的定义及使用、开域语句with
6、 指针的定义与使用:
^ :定义指向某类型的指针变量,该变量存放内存地址。
取出该变量存放的内存地址所指向内存变量的值
@ :取变量的内存地址。常用于对指针变量赋值。
这些最基本的语言基础,是“读程序写结果”的立根之本。只有熟练掌握这些语言基础,才能看懂与之相关的程序。
二、 基本解法
NOIP初赛“读程序写结果”有比较多的基础题,特别是近三四年,经常出现。这类题,只要在了解语言,特别是了解以上介绍的这些基础知识,再通过“人脑模拟程序”的方式,进行简单的数学运算,就可以得出答案。考查的是选手对语言基础的掌握情况,以及计算的细心与耐心。以下,选例讲解:
[选例一 NOIP2008提高组第一题]
var
i,a,b,c,d:integer;
f:array[0..3] of integer;
begin
解答过程如下:
for i:=0 to 3 do
f[0]
f[1]
f[2]
f[3]
read(f[i]);
9
19
29
39
a := f[0] + f[1] + f[2] + f[3];
a=9+19+29+39=96
a := a div f[0];
a=96 div 9=10
b := f[0] + f[2] + f[3];
b=9+29+39=77
b := b div a;
b=77 div 10=7
c := (b * f[1] + a) div f[2];
c=(7*19+10) div 29=4
d := f[(b div c) mod 4];
d=f[77 div 4 mod 4]=f[2]=19
if (f[(a + b + c + d) mod 4] > f[2]) then
f[(10+7+4+19) mod 4]=f[0]
begin
f[0]=9<29=f[2] 条件不成立
a := a + b;
writeln(a);
end else
begin
c := c + d;
c=4+19=23
writeln(c);
输出答案 23
end;
end.
输入:9 19 29 39
输出:___________
[简析]这种题,近三四年每年都出现,基本上可以说是增加选手信心的送分题。细心顺着语句,简单模拟一遍,答案自然就出来了。需要强调的是,解题过程中千万别“看轻”这类题,掉以轻心、粗心大意。务必仔细认真,特别是看清楚变量,不要张冠李戴、以桃代李。如果大家都能得分的,不小心失分了,那是最大的损失!建议,如上把解答的过程直接详尽清晰地写在程序的右边,不容易错,也便于检查。
[选例二 NOIP2007提高组第二题]
program s402;
var a,b:integer;
x,y:^integer;
procedure fun(var a,b:integer);
var k:integer;
begin
k:=a; a:=b; b:=k;
end;
begin
a:=3; b:=6;
x:=@a; y:=@b;
fun(x^,y^);
write('No.1:',a,',',b,' ');
fun(a,b);
writeln('No.2:',a,',',b);
end.
输出:
[简析]主要考查的是指针的简单使用以及子程序的值参概念。@为取变量的内存地址,x:=@a,也即x 存放的就是变量a的内存地址。x^表示的是,内存地址为x的整型变量的值,其实也即变量a的值。所以,变量a与变量x^是完全等价的,也即两个调用是完全一样的。结合值参概念,容易知道其结果是:No.1:3,6 No.2:3,6
这题的关键是指针的基本使用,尤其是取地址@。
请思考以下程序的输出结果:
program s402;
type point=^integer;
var a,b:integer;
x,y:point;
procedure fun(a,b:point);
var k:integer;
begin
k:=a^; a^:=b^; b^:=k;
end;
begin
a:=3; b:=6;
write('No.1:',a,',',b,' ');
x:=@a; y:=@b;
fun(x,y);
writeln('No.2:',a,',',b);
end.
输出:
[选例三 ]
Program ex3;
var n,i,j:Longint;
s:Array [1..100] of integer;
begin
readln(n);i:=0;
Repeat
inc(i); if odd(n) then s[i]:=1 else s[i]:=0;
n:=n SHR 1;
Until n=0;
for j:=i downto 1 do write(s[j]);writeln;
end.
输入:2009
程序运行的结果是:
[简析]本题的关键是清楚子程序inc(i)、odd(n)的含义,以及运算符shr的运算结果,特别是运算符shr的含义,如果不知道,那这题就没得做了。函数odd(n)是判断n是否为奇数,而shr是按位右移,高位补零。“直译”程序的意思是:n为奇数s[i]为1,否则s[i]为0;接着用右移运算,消去n化为二制进后的最低位。直到n为零。 其实,每次消化的n的二进制位最低位,就直接保存在数组s中。由此可知,其实输出的就是n转换为二进制后的结果:11111011001.
小结:全面掌握语言基础,是“读程序写结果”的立根之本。如果碰到哪个函数不清楚,哪个运算符或符号不了解的,那么相关题目就会当场挂掉。“读程序写结果”,一题就是8分,因这种原因而失分是痛中之痛。基于这点,在NOIP初赛前,全面复习相关基础知识是必要的、也是重要的。
读程序写结果之起步篇
上一讲我们全面介绍了语言基础,本讲将进入实质的“技巧化”训练。“读程序写结果”,解决这类问题的方法,总体说来有两大类:一是从宏观上,了解程序的目的和大致算法;二是从微观上,人脑模拟程序的实际执行过程。在实际应用中,常常要综合运用。为了便于探讨,下面列举了八种技巧。需要说明的是,每种技巧也不是独立的,在实际使用过程中,经常需要交叉混合应用。
技巧一:人工模拟,列表跟踪变量的变化过程
[选例四 NOIP2001提高组第二题]
program gao7_2;
var p,q,s,t:integer;
begin
readln(p);
for q:=p+1 to 2*p do
begin
t:=0;s:=(p*q)mod(q-p);
if s=0 then
begin t:=p+q+(p*q)div(q-p);
write(t:4);
end;
end;
end.
输入12 输出
[解析]:考查的是,学生对基本语句的阅读理解能力。通过简单的手工模拟可迅速得到答案。由于变量的递变次数较多,为了让手工模拟计算机执行过程更清晰,建议建立如下变量值递变列表:
q
13
14
15
16
17
18
s
12*13 mod 1
12*14 mod 2
12*15 mod 3
12*16 mod 4
12*17 mod 5
12*18 mod 6
s为零
是
是
是
是
否
是
t
181
110
87
76
66
续表:
q
19
20
21
22
23
24
s
12*19 mod 7
12*20 mod 8
12*21mod 9
12*22mod 10
12*23mod 11
12*24mod 12
s为零
否
是
是
否
否
是
t
62
61
60
也即输出结果为:181 110 87 76 66 62 61 60
列表的好处就是可以清晰的表示各变量在不同时刻的递变情况。在变量不是太多,循环体执行次数有限的情况下,可大力提倡这种方法。
技巧二:化整为零,分割处理
阅读程序时,有些程序比较长,依据循环或子程序可以明显地将其分为几个“段”,这时我们可以化整为零,充分利用程序结构将其分割成若干个子程序段,再集中精力,分别对每个程序段进行独立处理、理解,逐一解决。
[选例五 NOIP2002八届提高组第二题]
1
program Gxp2;
2
var i , j , s ,sp1 : integer ;
3
p : boolean ;
4
a : array[1..10] of integer ;
5
begin
6
sp1:=1; a[1]:=2; j:=2;
7
while sp1<10 do
8
begin
9
j:=j+1; p:=true;
10
for i:=2 to j-1 do
11
if (j mod i=0) then p:=false;
12
if p then begin
13
sp1:=sp1+1; a[sp1]:=j;
14
end;
15
end;
16
j:=2; p:=true;
17
while p do
18
begin
19
s:=1;
20
for i:=1 to j do s:=s*a[i];
21
s:=s+1;
22
for i:=2 to s-1 do
23
if s mod i=0 then p:=false;
24
j:=j+1;
25
end;
26
writeln(s); writeln;
27
end.
输出:
[解析]:这程序,明显可分为两段进行处理:第一段6至15行,第二段16至25行。分别处理如下:
从第一个while循环(第7行至第15行)容易知道,数组a里放的是最小的十个素数,即:
i
1
2
3
4
5
6
7
8
9
10
a[i]
2
3
5
7
11
13
17
19
23
29
第二个while循环(第17行至第25行),所要表达的含义也不难理解,就是“找到一个最小的合数,该合数由殊多最小素数累积加1构成”,理解好两个“程序段”,我们也就理解了整个程序。于是演算如下:
S1=2*3+1=7 ……………………素数
S2=2*3*5+1=31 …………………素数
S3=2*3*5*7+1=211
如何判断“211”是否素数呢?由素数的概念可知,如果211不被小于或等于的整数部分(即14)的素数整除,则为素数。S3的构成可知,211不被2、3、5、7整除,同时易知211 mod 11=2、211 mod 13=3,所以211是素数
S4=2*3*5*7*11+1=2311
易知502=2500,所以如果2311不被小于50的素数则为素数。
i
13
17
19
23
29
31
37
41
43
47
2311 mod i
10
16
12
11
20
17
17
15
32
8
用同样的方法可得出小于50的其它素数并检测2311可得:
所有余数不为零,故2311为素数
S5=2*3*5*7*11*13+1=30031
从素数17开始验证可得下表:
i
17
19
23
29
31
37
41
43
47
53
59
mod
9
11
16
16
23
24
19
17
45
33
0
30031 mod 59=0,所以不是素数,也即本题输出结果为:30031
这道题虽然结构简单,而且所要表示的程序目的也容易理解,却让许多Oier叫苦不迭——需要手算的循环次数太多。最难的在于“求余”步骤太多了(其实也就二十来步)。主要考查的,除了理解程序的能力外,更重要的,应该是选手们“坚持不懈”的精神(这种精神,做信息学奥赛的特需要),同时可以也让解题者体会到“探索——山重水复”,进而“成功——柳暗花明”的成就感。
技巧三:通过列表模拟,大胆猜想并确定程序目的
完全“机械模拟”存在明显的缺陷:速度太慢且容易人为失误。“模拟”的关键是为了不模拟。即:通过“模拟”,了解程序变量的含义、清理程序结构、体会程序执行的最终目的,进而“超越”程序的执行,直接或迅速手算出程序的输出结果。
[选例六 NOIP2001提高组第三题]
program gao7_3;
var i,j,h,m,n,k:integer;
b :array[1..10]of integer;
begin readln(n);
for i:=1 to 10 do begin
m:=n;j:=11;
while m>0 do begin
j:=j-1;
b[j]:=m mod 10;
m:=m div 10
end;
for h:=j to 10 do n:=n+b[h];
end;
writeln(n);
end.
输入1234 输出:
[解析]:
手工模拟部分:
For循环,i为循环变量;M:=N、J:=11:暂不能理解;while循环。这个循环的作用大部分同学也较清楚,就是把M进行拆位,并把各位数字依次存入数组A。由此也知:以上M:=N是为了保护N的值,J:=11是表示数组A的下标。如M=1234,执行到退出while循环时,J的值为7,A的值为:
H
…
7
8
9
10
B(H)
…
1
2
3
4
for h:=j to 10 do:也是循环。容易理解该循环的功能是在N的基础上累加数组A中下标从J到10的各值。该循环退出,N的值为1234+1+2+3+4,即:1244;结束外重循环,返回,i=2;
重新执行语句(M:=N;J:=11),这时M的值为“1244”,J的值还是11,容易得:
数组A的值为:
H
…
7
8
9
10
A(H)
…
1
2
4
4
所以K=2时,N的值为1244+1+2+4+4,即:1255。
至此应该可以理解程序的执行目的:即把N的数位拆开,再加起来,对新的N做同样的操作,反复10次。如果还进行“机械模拟”的话,会影响解题的速度,同时也增加人为失误的可能性。所以无需再“模拟”程序,可迅速手算出最后结果。
“超越” 手算部分:
K
3
4
5
6
7
8
9
10
N
1255+1+2+5+5
(1268)
1268+1+2+6+8
(1285)
1301
1306
1316
1327
1340
1348
也即本例输出结果为:1348
技巧四:善于把握关键语句(段)或过程名称
算法发展至今,许多功能实现的程序段是经典而通用的。如:选例五的判断素数、选例六的拆位(即进制转换的本质),还有累加、累乘、排序、求最大值最小值,以及一些经典算法。碰到类似这样的程序段,我们可以有依据地判断其程序目的。
“可读性”是程序优劣的一个重要指标。程序书写,为了其可读性,很多时候,变量名及子程序的名称,往往能“见文生义”。通过这些名称,有时很容易猜到子程序的功能(或变量的含义),起到“事半功倍”的效果。
[选例七 NOIP1998提高组第二题]
program ex5;
const n=10;
var s,i:integer;
function co(i1:integer):integer;
var j1,s1:integer;
begin
s1:=n;
for j1:=n-1 downto n-i1+1 do
s1:=s1*j1 div (n-j1+1);
co:=s1;
end;
begin
s:=n+1;
for i:=2 to n do s:=s+co(i);
writeln('s=',s);
end.
[解析]:程序结构简单,关键是弄清楚子函数co的功能。手工模拟一下:
co(2):j1:=9 downto 9
s1=10*9/2
co(3):j1:=9 downto 8
s1=10*9/2*8/3
其实,到这应该可以知道程序的目标了,再结合函数名称为co,容易想到组合数。再看一个:
co(4):j1:=9 downto 7
s1=10*9/2*8/3*7/4
=10*9*8*7/(4*3*2*1)=C(10,4)
循环变量i是从2开始,累加到n。s的初值为1+n。如果我们能联想到C(n,0)=1、C(n,1)=n,则该程序输出即为:C(n,0)+ C(n,1)+ C(n,2)+…+ C(n,n)=
小结:人工模拟的目的是为了理解程序的目的。为此,我们在模拟的过程中,应该结合自己已有的知识,并明察秋毫地接收一切有用的信息(包括函数名变量名),大敢而又细致地猜测并验证程序的目的。在了解并确定程序目的后,许多时候就可以超越程序本身,直接写出程序的执行结果。所以,了解程序的目的是关键。
读程序写结果之进阶篇
上一讲,我们就“读程序写结果”解题的一般方法,做了个简单的探讨与总结。本讲,我们共同来研究两个特例:有关“字符(串)的操作与递归调用。
技巧五:字符串的处理
字符串操作,无论是NOIP初赛还是复赛,近些年来,频频出现,非常明确地提醒了选手,在准备NOIP竞赛时,必须充分地重视并认真彻底地把字符(串)操作了解清楚。
在Pascal中,有关字符的类型有两种:char、string。特别要注意类型string与字符数组的异同点:(假设变量说明为: a:string; b:array[1..100] of char)
相同点:都可以用下标变量(形如a[i]、b[i])进行访问。
不同点:ord(a[0])的值,固定为字符串a的长度(操作影响到字符串长度时,应注意保持该值的有效性);字符串a可以进行整体输出输入并可以使用系统有关字符串的过程与函数,而字符数组不行;类型string所能表示的字串长度为1到255,而字符数组不受此限制。
有关字符(串)的函数与过程,请参阅“基础篇”。值得一提的是,常用字符的ASCII码为(括号内为ASCII码,从小到大排列):…’0’(30H)…’9’…’A’(41H)…’Z’…’a’(61H)…’z’…
对字符(串)的处理,要求选手能结合字符(串)有关知识,灵活使用前面讲过的技巧。以下选例说明字符(串)操作在“读程序写结果”中的应用。
[选例八 NOIP2008提高组第四题]
1
var s:string;
2
i,j,len,k:integer;
3
begin read(s);len:=length(s);
4
for i:=1 to len do
5
if (ord(s[i])>=ord('A')) and (ord(s[i])<=ord('Z')) then
6
s[i] := chr(ord(s[i]) - ord('A') + ord('a'));
7
for i:=1 to len do
8
if (ord(s[i])<ord('x')) then s[i]:= chr(ord(s[i])+3)
9
else s[i]:= chr(ord(s[i])-23);
10
write(s);write('/');
11
for j:=1 to 3 do begin
12
i:=1;
13
while i<=len-j do begin
14
s[i]:=s[i+j];
15
i:=i+j;
16
end;
17
end;
18
writeln(s);
19
end.
输入:ABCDEFGuvwxyz
输出:____________
[解析]:应该算基础题。程序有点长,我们可以根据结构分为以下几个程序段进行分析:
第4至6行:这个循环是依次判断字符是否为大写字母,若是,则改为小写。执行后s的值为:abcdefguvwxyz;
第7至9行:注意分界点(对比对象)是字母’x’,运算后,s为:defghijxyzabc
第10行:输出:defghijxyzabc/
第11至17行:循环递变,j的值比较小,直接列表模拟!注意15行:变量i的步长。
变量j
1
2
3
递变前的s
defghijxyzabc
efghijxyzabcc
gfihxjzybaccc
递变后的s
efghijxyzabcc
gfihxjzybaccc
hfizxjaybcccc
实现功能
依次移位
奇数位移位
1ß4ß7ß10ß13
第21行:输出:hfizxjaybcccc
虽然本程序给人第一感觉:程序长、似乎挺难的,但经过简单的分割处理,可以看出,每个程序段的功能都是显而易见的。“一切反动派都是纸老虎”!在战略上可以藐视这类题,但在战术上应该重视细节。克服长程序给人的心理压力,沉下心来,仔细分析,认真运算,一切就迎刃而解。
[选例九 ]
1
Program t4;
2
var n,i,j:integer;str:string;
3
a:Array [1..21] of string;
4
t1,t2:longint;code:integer;
5
begin
6
readln(str);
7
n:=0;
8
while str<>'' do begin
9
i:=pos(' ',str);
10
n:=n+1;
11
if i>0 then
12
begin a[n]:=copy(str,1,i-1);delete(str,1,i);end
13
else begin a[n]:=str;str:='' end;
14
end;
15
for i:=1 To n-1 do
16
for j:=1 To n-i do begin
17
val(a[j]+a[j+1],t1,code);val(a[j+1]+a[j],t2,code);
18
if t1<t2 then begin
19
a[21]:=a[j];a[j]:=a[j+1];a[j+1]:=a[21];
20
end;
21
end;
22
for i:=1 To n do write(a[i]);writeln;
23
end.
输入:9 3 34 321
程序运行的结果是:
[解析]这题用到的字符(串)子程序比较多,有需要的,请参阅“基础篇”。
第8至14行构成循环的功能是什么呢?第9行,取空格的位置;继而,如果空格存在的话,把空格前的字符串赋予a[n],并在串str中把至空格为止的字符全部删除;若空格不存在,则把str赋予a[n],令str为空串。所以,实际上,它是以空格为分隔,把各数字字串,分别置于数组a中,即a={'9'、'3','34','321'}。
第15到21行,冒泡排序经典程序段,不同的是,大小的评价标准不一样。它是以相邻两数字字串连接在一起的数值大小为衡量标准。以a[j]= '3'、a[j+1]= '34'为例,执行完第17行的语句后可求得t1=334、t2=343,由于t1<t2,交换a[j]、a[j+1]。这样从“大”到“小”排列后依次输出结果:9343321。
也即,输出由这些数字字串排列生成的最大数值。
技巧六:子程序调用
在处理子程序调用时,应注意参数的传递,特别是区分值参与变参。如果存在全程变量与局部变量交叉使用时,要注意变量的作用域。必须清楚,只有在调用的子程序完全执行完毕,才能返回调用该子程序的程序中继续执行。
递归调用是子程序调用的重点,也是难点。如何有效正确计算递归程序的结果呢,下面举几个例子说明一下:
[选例十]
procedure w(num:integer);
begin
if num>=14 then
w(num div 14);
write(num);
end;
begin
w(2009);
end.
程序运行的结果是:
[解析]具体执行过程如下:(注意箭号顺序)
1
2
3
4
5
三个write语句,依次输出:101432009
本例应用大括号的方式全部展开递归调用的过程,只要谨记所调用的子程序执行完毕方能返回调用该子程序的程序中继续执行,严格把关语句的执行顺序就可以了。
只是,很多时候,我们没法,或者很难手工全部展开递归的详细过程,这时,一般可以采用“自底向上”,从递归边界出发,递推出结果。如:
[选例十一 ]
function s(n,k:integer):longint;
begin if (n=k) or (k=1) then s:=1
else s:=s(n-1,k-1)+k*s(n-1,k);
end;
begin writeln(s(7,5)); end.
输出结果:
[解析] 程序很短很简单,但是如果我们用类似上例的方法来模拟展开该递归的执行调用过程,会碰到前所未有的困难。原因很简单,递归调用的次数太多了。碰到这种情况时,我们可以从递归的边界出发,进行递推求解。具体求解过程如下:
1、边界赋值:s[n,n]=1;s[n,1]=1
2、递推求解过程:
S[3,2]=s[2,1]+2*s[2,2]=3
S[4,2]=s[3,1]+2*s[3,2]=7;s[4,3]=s[3,2]+3*s[3,3]=6
S[5,3]=s[4,2]+3*s[4,3]=25;s[5,4]=s[4,3]+4*s[4,4]=10
s[6,4]=s[5,3]+4*s[5,4]=65;s[6,5]=s[5,4]+5*s[5,5]=15
S[7,5]=s[6,4]+5*s[6,5]=65+5*15=140
整个递推过程,可记录如下表格:
k
n
1
2
3
4
5
1
1
2
1
1
3
1
3
1
4
1
7
6
1
5
1
25
10
1
6
1
65
15
7
1
140
也即本例输出结果为:140
值得注意的是,我们只要求出表格中的数据即可,其它空白的,无需求解。为了节省时间,避免不必要的人为错误,所做的运算越少越好。那么哪些是该算的、哪些是无需计算的,也即如何只进行有效计算呢?这问题,留给读者自己思考。
小结:字符(串)操作,除了要求选手要有扎实的语言基础并了解相关字符串知识点外,多数还体现了对选手细心、耐心及毅力品质的考核。
展开阅读全文