下面根据程序结构,把程序分成几块,逐个研究。最主要的程序段是两个WHILE循环中套一个FOR循环,三重循环!!!其实,最外面一层很明确:判断什么时候结束(y=0)。前后都很简单,是一些变量和数组的初始化、输入、输出等,下面重点剖析核心程序段。
1) x:=3465; y:=264; jk:=20;
for j1:=1 to 20 do a[j1]:=0;
输入与变量、数组的初始化,不要管它。
2)while y<>0 do
begin
y1:=y mod 10;
y:=y div 10;
while y1<>0 do
begin
<< g:=x;
for e:=Jk downto 1 do
begin
g:=g+a[e];
a[e]:=g mod 10;
g:=g div 10
end;
>>
y1:=y1-1
end;
jk:=jk-1
end;
3)
j1:=1;
while a[j1]=0 do j1:=j1+1;
for Jk:=j1 to 20 do write(a[jk]:4);
writeln
从前往后,找到<>0的位置开始,输出数组元素的值(输出结果),也不要管它。
块2最重要。
它的思想是:每次取Y的最第位y1,执行<<运算>>y1次,每次把jk减1。
现在最重要的是<<运算>>中的在干什么?
注意到最后输出的a[],要留意a[]的变化!
a[e]总是取个位(g mod 10),g每次少一位,和a[e-1](别忘了e在循环!)相加...
难道是...高精度加法???RIGHT!
它执行了y1次,y1每次都是y的个位!对了。程序就是在做x*y
所以答案就是 3465*264=914760
再看它的输出格式,输出的应该是:___9___1___4___7___6___0
其实有经验的话,看到g这个变量名和g:=g+a[e]; a[e]:=g mod 10;这几个标志句子。就可以一下子知道程序的用意了。
总结一下本题:重点考循环嵌套的执行过程以及对除法运算中的商、余数的基本方法;主要程序段可以通过列表了解变量的变化情况;要细心、耐心;题目的本身没有多少值得研究的价值!!!但有些题目纯粹考算法思路,如下面的例子:
2. 算法题
program ex2;
var i,j,n:longint;
b:array [0..31] of 0..1;
begin
n=1999;
i:=0;
while n<>0 do
begin
b:=n mod 2;
i:=i+1;
n:=n div 2
end;
for j:=i-1 downto 0 do write(b[j]);
end.
输出什么?
解答:很明显,是把十进制整数转换成二进制数,所以输出11111001111
3.有些题目则是考数学,或根据一些基本规则重复地做某个操作。如:
program exp1 (imput,output);
var i,,s,max: integer;
a:array [1..10] of integer;
begin
for i:=1 to 10 do read (a);
max:=a[1] ;s:=a[1];
for i:=2 to 10 do
begin
if s<0 then s:=0;
s:= s+a;
if s>max then max:=s
end;
writeln(‘max=’, max)
end.
输入:8 9 -1 24 6 5 11 15 -28 9
输出:max=
解答:本题主要做累加:s:= s+a;再根据结果打擂台。
但最关键的语句是:if s<0 then s:=0;它的作用是把s<0的前若干个元素的和值屏蔽掉(置0)。了解了这一点,题目就很简单了。步骤如下:
s<0? s=8 s>max? max=8;
I=2 N 8+9=17 Y max=17;
I=3 N 17-1=16 N max=17;
I=4 N 16+24=40 Y max=40;
I=5 N 10+6=46 Y max=46;
I=6 N 46+5=51 Y max=51;
I=7 N 51+11=62 Y max=62;
I=8 N 62+15=77 Y max=77;
I=9 N 77-28=49 N max=77;
I=10 N 49+9=58 N max=77;
所以结果为:77。
小结:本质是求一个n长的整数数列的连续x长的子序列,要求子序列的和最大!
注意:s和max!!!另外本题给的输入数据比较简单,所以有很多人没有完全懂也算对了结果,把数据改成如下:-1 12 -103 65 34 -4 -27 8 -1234 9,问结果是多少呢?答:9!!!
4.考子程序的调用,尤其是递归或带参数(值参与变量型参数),如:
PROGRAM EX3;
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.
解答过程:
(1) 如果有子程序,一般先读主程序,了解主程序什么时候调用子程序?干什么的?调用了多少次?本题调用了n-1次,并且累加函数的返回值!
(2) 再单独阅读子程序,分析变量、参数和返回值,确定它的功能。本题函数猛一看好象比较复杂,不过是通过一个循环结构完成累乘和累除,下面再具体分析!
(3) 通过列表,观察子程序中的变量变化情况,以便找出规律,确定子程序的作用。本题如下:
CO(2)=10*9/2
CO(3)=10*9*8/2/3
CO(4)=10*9*8*7/2/3/4
……
发现,好象是组合数学的公式:CO(i)=10!/(i!*(10-i)!)
即:C(m,n)=m!/(n!*(m-n)!)=m*(m-1)*……*(m-n+1)/n!
(4) 所以结果很明确:C(10,0)+ C(10,1)+……+C(10,9)+ C(10,10)=722
总结:灵感来源于丰富的数学基础和经验!
完善程序(30分=2*15)
这部分题目得分率很低。没关系,尽量做吧。实在不会把一些简单的填好就行了。有些题目即使不懂也能填出来:)比如:i:=i+1;i:=0;
注意经常问自己程序中有没有做下面的事:
1)初始化(i:=0; j:=0; for i:=1 to n do a:=0之类的)
2)一些明显的动作:
a.结果没有储存在需要的地方。
b.累加器没有做加法
c.输出
3)关键动作。
例如交换排序程序的“交换”操作等很明显需要完成的操作。
分析方法和写运行结果类似,注意分析变量和程序结构,理解变量和模块的作用是解题的关键。
不熟练是不妨用自然语言描述一下。
一般的解题步骤:
1. 仔细阅读程序的目的和算法、数据结构的描述!
千万不要一激动一紧张,题目都没看透彻!!!
2. 把程序中的变量和题目中数据结构描述结合起来,记住关键变量的作用!
有时就能填出一些简单的空!!!不信?!!!
3. 结合问题的算法描述和要求及步骤,把程序划分成几段,每段完成指定的功能!
千万不要忘记:这是完善程序,不是让你自己编!!!一定要不断地结合题意和程序。
4. 逐步解决每段!
注意不要因为个别空而影响你对整个程序的把握,不知道的,先空在那儿,把知道的填好,最后再收拾那些难点!!!
注意一定要提醒自己:程序中有没有做那些最基本的事。
5. 做完后,不要忘了把程序从前往后读两遍,看看是否完成了题目的任务;还要检查一些细节性的问题,如“>”还是“>=”,是n-i,还是n-i+1?
下面举例给予说明。
1.基础题(算法、数据结构很清楚、很朴素,送分!)
[程序的说明]
本程序对随机产生的100个0到50之间的随机数用一个数组存放后进行排序,然后再将其中重复出现的数进行删除,只保留一个,使得剩下的数中任何两个都不相同且连续存储在原数组中。
const maxn=100; type arraytype=array [1..maxn] of integer; var i,j,temp,current,tail:integer; a:arraytype; begin randomize;
for i:=1 to maxn do a:=random(51); for i:=1 to __ ① do for j:= _ ② _ to maxn do
if a<a[j] then
begin temp:=a;a:=a[j];a[j]:=temp end;
for i:=2 to maxn do
if __ ③ __ then a:=-a;
tail:=0;current:=1;
while _____ ④ _____ do
begin
while a[current]<0 do current:=current+1;
tail:=tail+1;
a[tail]:= __⑤__ ;
current:=current+1
end;
if ___⑥__ then begin tail:=tail+1; a[tail]:=0 end;
for i:=1 to tail do write(a:5);
writeln
end.
[解答]
程序的思想已经不能再清楚了,因为要排序(很明显是冒泡排序),所以:
①maxn-1
②i+1
那么如何删除呢?先分析一下变量的含义,current和tail分别表示队列的头尾指针。再看删的过程:好象是先做标记(置为负!),然后从队首current开始找第1个没被做标记(>0)的数,把它放到当前队尾tail的下一个位置。所以:
③a=abs(a[i-1])
④(current<=maxn) and (a[current]<>0)
⑤a[current]
⑥(a[tail]<>0) and (a[current]=0)
2.关键变量+特定的思想方法+灵感(1995年初中组2)
找出小于33的6个正整数,用这些整数进行加法运算,使得包括原来的整数在内能组成尽可能多的不同整数。
例如:用2,3,5这三个数能可组成下面的数:
2, 3, 5, 2 + 3 = 5(但5已经存在)
2 + 5 = 7, 3 + 5 = 8, 2 + 3 + 5 = 10
所以用2,3,5能组成6个不同的数。
程序要求:输出所选的这6个数,以及能组成不同整数的个数。
算法提要:选择的这6个数,用来组成数时应该尽可能不重复,引入数组A保存找出的这6个整数。
主要程序段:
A[1] := 1; t := 0;
For i := 2 to 6 do
begin
_________①________;
for j := 1 to i - 1 do s := ______②_______;
a := _______③_______;
end;
For i:=1 to 6 do
Begin
t:= ______④______ ;
WRITE(a, ' ');
End;
Writeln('能组成不同整数的个数:', t)
解答:首先,根据蓝色的程序段,我们应该判断出③应该和s相关,而②是为了计算s,所以本题的关键是变量s的含义。
不要着急,我们研究一下题目的例子和算法提要,发现:选择的6个数(a)应该尽可能不重复;且a>a[i-1]而a还要尽可能小;假设现在已求出了a[1]…a[i-1],那么为了满足“能组成尽可能多的不同整数”,则a应该取a[1]+a[2]+…+a[i-1]+1,这样必然要设一个累加器,再看看程序:)还真是!!!所以得到:①初始化s:=0; ②累加s+a[j]; ③赋值,注意多加1:s+1;
那么④怎么填呢?它表示能组成的不同整数的个数,那为什么要扫描一遍数组呢?感觉也应该是累加!其实我们应该充分发挥上面已填好的程序段,发现:6个数为:1 2 4 8 16 32(哦,难怪说小于33!让我更加坚信上面做的是对的!),很明显是二进制数的问题吗?本质上就是一个求一个6位的二进制数最多能表示多少状态?答案为:20+21+22+23+24+25=1+2+4+8+16+32。不要激动,看题目④填什么?累加:t+a。
3.复杂的问题描述+简单的程序+细心地处理细节问题(1995年高中组3)
设有一个实数,以字符串形式存放于数组x中,用x:array[1..N]of char表示。其中x[1]若为'-',表示负数;若为'+'、'.'或' ',则表示正数。若为数字,也认为是正数。
例如 x=(' ','2','0',' ','3','.','5','%') 则表示203.5
x=('-','1','.',' ','2','0','%') 则表示-1.2
约定:在字符串x中,除x[1]外,其后可以包含有若干个'.'与' ',但仅以第一次出现的为准,空格不起任何作用,并以字符'%'作为结束标志。
程序要求:将输入的字符串还原成实数输出(小数点后无用的0应除去),还原的结果以下列形式存放(不需要输出)。
F:数符。正数放0,负数放1。
A:array[1..N] of integer; 存放数字,不放小数点。
K:表示A中有效数字的个数。
J:表示小数点后的位数。
例如:数203.24,还原后结果的存放是:
F=0
A=(2, 0, 3, 2, 4)
K=5
J=2
又如:数-33.0740,还原后结果的存放是:
F=1
A=(3, 3, 0, 7, 4)
K=5
J=3
算法提要:x : array[1..10] of char;可放长度定为10;首先读入字符串,然后处理数的符号,在还原的过程中,需要判定整数部分与小数部分,同时去除多余的空格和小数点,并约定输入是正确的,不用作出错检查。
只要程序段:
For I := 1 to 10 do a[I] := 0;
For I := 1 to 10 do read(x[I]);
J := 0; f := 0; k := 0; b := 0;
If x[1] = '-' then begin
____________①____________;
____________②____________;
End
Else if x[1] := ' ' then I := 2
Else I := 1;
While ________③_________ do I := I + 1;
While __________④___________ do
BEGIN
If (x[I]>= '0') and (x[I]<= '9') Then begin
k:=k+1;
________⑤_______;
If b=1 then ______⑥_______;
end
Else if (x[I]='.') and (b=0) then b := 1;
I:=I+1
END;
If j>0 then while a[k]=0 do begin
__________⑦_________
__________⑧_________
End;
解答:显然,蓝色的程序段是用来处理实数的符号的,所以根据约定①应该是设置负数标记,即f:=1;根据else后面的句子,发现i为循环扫描的指针,所以②应该是确定下一位置,即i:=2;
③很明显是过滤掉空格!所以填:(x=’ ’)and(i<10);
④也很明显,一个大循环,判断字符串是否扫描结束,即:x<>’%’
再看看b变量的含义:根据else子句,发现b=1表示整数部分结束!所以⑤是把一个数字字符转换成数字填到数组a中(a[k]:=ord(x)-48);⑥填j:=j+1;(j表示小数点后的位数);
⑦⑧很明显,是处理有小数、并且有多余的0的情况,所以为:j:=j-1;k:=k-1;
小结:注意程序前前后后看,发现变量的作用和含义!!!
4.典型算法+数据结构(1996年高中组1/初中组3)
四色问题:
设有下列形状的图形:(N=8),其编号
为1,2,……,N。
图形之间的相邻关系用下面的邻接矩阵表示:
1 2 3 4 5 6 7 8
1 0 1 0 0 0 0 1 1
2 1 0 1 0 0 1 1 0
3 0 1 0 1 0 1 0 0
4 0 0 1 0 1 1 0 0
5 0 0 0 1 0 1 0 0
6 0 1 1 1 1 0 1 0
7 1 1 0 0 0 1 0 1
8 1 0 0 0 0 0 1 0
其中:1——相邻,0——不相邻。
[程序要求] 将上面图形的每一个部分涂上红(1),黄(2),蓝(3),绿(4)四种颜色之一,要求相邻的部分有不同颜色。
输入方式:邻接矩阵。
输出方式:区域、颜色。
[算法描述] 用数组R:ARRAY[1..N,1..N] OF 0..1表示相邻关系,S:ARRAY[1..N] OF INTEGER 表示颜色。
采用回溯的方法,首先给第一个图形涂上红色(1),然后在下面的图形中依次涂上其他颜色,当有矛盾时回溯解决。
[程 序]
PROGRAM EXP2(INPUT,OUTPUT);
CONST N=8;
VAR I,J,K:INTEGER;
R:ARRAY[1..N,1..N] OF 0..1;
S:ARRAY[1..N] OF INTEGER;
BEGIN
FOR I:=1 TO N DO
BEGIN
FOR J:=1 TO N DO READ(R[I,J]); READLN
END;
① ;I:=2; J:=1;
WHILE I<=N DO
BEGIN
WHILE (J<=4) AND (I<=N) DO
BEGIN
K:=1;
WHILE ② DO K:=K+1;
IF K<I THEN ③
ELSE BEGIN
④ ; I:=I+1; J:=1
END
END;
IF J>4 THEN BEGIN
I:=I-1; ⑤
END;
END;
FOR I:=1 TO N DO WRITELN(I,'',S[I])
END.
解答:邻接矩阵+回溯法,步骤略,答案如下:
① S[1]:=1;
② (K<I) AND (S[K]*R[I,J])<>J
③ J:=J+1;
④ S[I]:=J;
⑤ J:=S[I]+1;
5.子程序及参数(1999年初中组)
[问题描述]
下面程序的功能是从键盘读取A,B数组的元素,A,B数组均已从小到大排好序(无相同元素),现将A,B合并为数组C,同样要求数组C也是从小到大排好序(有相同元素时只保留一个)。
程序中N表示数组A,B的长度,i,j,k分别表示数组A,B,C的取数或存数的指针。
[程序清单]
program excp3;
const n=8;m=2*n;
type arr1=array[1..n]of integer;
arr2=array[1..m]of integer;
var a,b:arr1;c:arr2;i,j,k:integer;
procedure copy(x:arr1;var y:arr2;var i,j:integer);
begin
i:=i+1;
y:=x[j];
j:=j+1;
end;
begin
for i:=1 to n do read(a);readln;
for i:=1 to n do read(b);readln;
i:=1;j:=1;___________①________
while__________②__________do
if a<b[j] then copy (a,c,k,i)
else if b[j]<a then copy (b,c,k,j)
else begin
copy(a,c,k,i);
__________③__________
end;
while__________④___________do copy(a,c,k,i);
while__________⑤___________do copy(b,c,k,j);
for i:=1 to k do write (c:4);
writeln;
end.
解答:就本题而言,算法和题目本身对选手很清楚!线性表的归并操作在NOIP中考过多次,不管是用数组来操作还是用链表操作;甚至还有一些题目是它的变形(比如多项式的加法)。
本题中的copy过程是关键,好在题目并没有考到过程调用的语句(关键是参数的书写),所以下面只要理解了过程的作用和4个参数的含义,题目就会很容易了。
答案:① k:=0
② (i<=n)and (j<=n)
③ j:=j+1
④ i<=n
⑤ j<=n
6.特定的算法(1999年高中组2)
[问题描述] 用生成法求出1,2,…,r的全排列(r<=8)
[算法过程] 用数组:a:array[1..r]of integer ;表示排列;
初始化时,a[I]:=1(I=1,2,….f)
设中间的某一个排列为a[1],a[2],…a[r],则求出下一个排列的算法为:
(1) 从后面向前找,直到找到一个顺序为止(设下标为j,则a[j-1]<a[j])
(2) 从a[j]- a[r]中,找出一个a[k]比a[j-1]大的最小元素
(3) 将a[j-1]与a[K]交换
(4) 将a[j],a[j+1]……a[r]由小到大排序。
[程序清单]
program exp4;
const r=7;
var n,i,s,k,j,i1,t:intger;
a:array[1..r]of integer;
procedure print1;
var ik:integer;
begin
for ik:=1 to r do write(a[ik]:8);writeln;
end
begin
for i:=1 to r do __________①__________;
print1;
s:=1;
for i:=2 to r do s:=s*i;
s:=s-1;
for i:=__________②__________do
begin
j:=r;
while__________③_________do j:=j-1; 步骤1
k:=j;
for i1:=j+1 to r do
if __________④_________then k:=i1; 步骤2
t:=a[j-1];a[j-1]:=a[k];a[k]:=t; 步骤3
for i1:=j to r-1 do
for k:=i1+1 to r do
if __________⑤___________then 步骤4
begin
t:=a[i1];a[i1]:=a[k];a[k]:=t;
end;
print1;
end;
end.
解题步骤:
1) 首先,本题给出了关键而详细的算法说明,但没有给一个样例,下面我们结合一个中间状态数据,看看如何生成下一个状态数据。
1 8 7 3 4 6 5 2
经过4步后得到:1 8 7 3 5 2 4 6
这样,你对程序的逻辑过程就很清楚了!!!
2) 把程序分段:如上4中颜色。
红色的过程表示输出,没问题!
蓝色的显然是初始化,所以①填:a:=i;
绿色的表示统计总的方案数(并且已经输出了一个,所以-1);
紫色的程序段很明显是解决算法说明的4个步骤的,下面重点解决!
3) 看看4个步骤分别对应着哪些语句?注意对应和找关键动作!!!
②应该填:1 to s 或s downto 1,但不能写成2 to s;
③填:a[j-1]>a[j]
④填:(a[i1]>a[j-1]) and (a[i1]<a[k]) 复合条件缺一不可,经常考!
⑤填:a[i1]>a[k],显然是从小到大冒泡排序用。
小结:重视题目给出的每一个信息与程序中的哪些语句对应;如果没有样例,自己找一个典型的,运行运行找出规律;程序的分块!!!
7.数据结构题(1999年高中组试题)
[问题描述]求一棵树的深度与宽度。
[算法说明]树可用数组tree:array[1..n,1..5]of integer;
其中:tree[I,1]表示结点号;tree[I,2]——tree[I,5]所属结点
如上图可表示为:
1 2 3 4 0
2 5 6 7 0
3 8 0 0 0
4 9 10 0 0
5 0 0 0 0
6 0 0 0 0
7 11 12 0 0
8 0 0 0 0
9 0 0 0 0
10 0 0 0 0
11 0 0 0 0
12 13 0 0 0
13 0 0 0 0
在求解的过程中,用到数组G:ARRAY[1..N,1..7]OF INTEGER;
其中:G[I,1]表示父结点,G[I,2]表示层次,
G[I,3]表示本结点号,G[I,4]——G[I,7]表示子女结点;
同时,设2个指针SP1(取数指针),SP2(存数指针)
[程序清单]:
program exGp3;
const n=13;
var i,j,k,sp1,sp2,n1,n2,jmax,p:integer;
tree:array[1..n,1..5]of integer;
g :array[1..n,1..7]of integer;
begin
for i:=1 to n do
begin
tree[i,1]:=i;
for j:=2 to 5 do read (tree[i,j]);readln;
end;
sp1:=1;sp2:=1;g[1,1]:=0;g[1,2]:=1;g[1,3]:=1;
for i:=4 to 7 do g[1,i]:=tree[1,i-2];
while__________①_________do
begin
p:=g[sp1,2];n2:=g[sp1,3];_________②________;j:=4;
while _________③_________do
begin
n1:=g[sp1,j];j:=j+1;__________④_________;
g[sp2,1]:=n2;g[sp2,2]:=p;g[sp2,3]:=n1;
for i:=1 to 4 do g[sp2,i+3]:=tree[n1,i+1];
end;
__________⑤_________;
end;
writeln('maxd=',g[sp2,2]); j:=1;k:=g[1,2];jmax:=0;
for i:=2 to sp2 do
if __________⑥__________then j:=j+1
else begin
if j>jmax then jmax:=j;
__________⑦________;k:=g[I,2];
end;
if j>jmax then jmax:=j;
writeln('max1=',jmax);
end.
解答:
1) 本题的数据结构含义,首先要搞清楚:tree[i,j]存储编号为i的结点的第j号孩子(2<=j<=5,即最多4个孩子),tree[i,j]=0表示不存在;g[i,k]是一个队列,sp1为读取队列用的指针,sp2为存储队列用的指针。g[i,1] 存储i的父结点,g[i,2]存储i所在的层次,g[i,3]存储本结点的编号i,g[i,4],g[i,5],g[i,6],g[i,7]存储i的孩子结点编号。列出g的表格形式,以便更加直观!!!
2) 程序关键在红色和蓝色的两段。先看红色段,①显然表示队列非空时做……,所以应该填:sp1<=sp2;变量p是用来存放层次的,所以②填:p:=p+1;为该结点的孩子结点准备好层次;n2是表示当前处理的结点号,n1是表示n2的孩子结点号(用j循环),③这个地方的循环是为了遍历本结点的所有孩子,所以③填:g[sp1,j]<>0;那么④⑤干什么呢?不要忘记一个重要的事情,队列的读取都需要移动指针(sp1,sp2),所以正好,④为下面的存入操作作准备,即sp2:=sp2+1;⑤为下一个结点的遍历操作作准备,即读指针下移:sp1:=sp1+1;
3) 下面看看蓝色的程序段,目的很明显是为了输出。再注意题目的目的:输出树的宽度和深度!深度简单,其实就是g[sp2,2]。题目也没有考你!!!那么剩下的问题显然就是为了求宽度。方法也很简单,就是求每一层的宽度(即g[I,4]~g[I,7]中的非0个数,或者按本题的方法是看g[I,2]中最多有几个数相同),然后打擂台找出最大值。因此,⑥填:g[I,2]=k,计算层次相同的元素个数放在j中,用j与jmax打擂台,注意一层完了,j值要还原,宽度至少为1,所以⑦填:j:=1;
(2000年高中组试题)问题与上一样,只是写程序的人不一样,具体的风格不同而已。
小结:本题其实很重要的是队列的操作,经常出现的还有:堆栈操作、模拟法(解决递归等问题的现场保护和恢复)。
2001-2002完善程序题目介绍!
略。(完)