pascal递归算法noip竞赛材料.docx
《pascal递归算法noip竞赛材料.docx》由会员分享,可在线阅读,更多相关《pascal递归算法noip竞赛材料.docx(29页珍藏版)》请在冰豆网上搜索。
![pascal递归算法noip竞赛材料.docx](https://file1.bdocx.com/fileroot1/2023-2/25/06aa67dd-a28d-478a-bb31-c1767dcfa211/06aa67dd-a28d-478a-bb31-c1767dcfa2111.gif)
pascal递归算法noip竞赛材料
信息学竞赛―――递归算法
一个过程(或函数)直接或间接调用自己本身,这种过程(或函数)叫递归过程(或函数).递归程序包含递归和递推两个过程,这两个过程又都是根据一个递推公式进行的。
一般来说,能够用递归解决的问题应该满足以下三个条件
①需要解决的问题可以化为一个或多个子问题来求解,而这些子问题的求解方法与原来的问题完全相同,只是在数量规模上不同;
②递归调用的次数必须是有限的;
③必须有结束递归的条件(边界条件)来终止递归。
例1、楼梯共有N阶台阶,上楼可以以一步上一个台阶,也可以一步上二个台阶。
编一个程序计算上N阶台阶,共有多少种走法?
programstair(input,output);
vars,n:
integer;
functionf(n:
integer):
integer;
begin ifn<3thenf:
=n
elsef:
=f(n-1)+f(n-2);
end;
begin
readln(n);
s:
=f(n);
writeln('s=',s);
end.
例2、骨牌铺法
有1*n的一个长方形,用一个1*1、1*2、1*3的骨牌铺满方格。
例如当n=3时为1*3的方格。
此时用1*1,1*2,1*3的骨牌铺满方格,共有四种铺法。
图4.4.3列出了四种铺法。
●
●
●
●
●
●
●
●
●
输入n(0<=n<=30) 输出 铺法总数
分析:
这道题可以采用猜测法,从具体的n=1,2,3,......开始,列举出结果,根据列举的部分结果进行猜测,推导出公式。
这个猜测推导过程留给读者完成。
问题是:
这种方法中“猜”和“凑”的成分比较多,容易出错。
我们不妨采用组合数学常用的待定系数进行归纳和推导。
设推导公式如下:
f(n)=a*f(n-1)+b*f(n-2)+c*f(n-3)+d*f(n-4)+....(a,b,c,d...是常系数)
即1*n的长方形的铺法由全部(a种)1*(n-1)的长方形铺法总数加上全部(b种)1*(n-2)的长方形的铺法总数加上全部(c种)1*(n-3)的长方形的铺法总数......注意排除重复情况。
(1)将n格分成1格和n-1格,计算f(n-1)的系数a。
右端1格的铺法有一种(a)。
显然,在一格中只有一种铺法,即f(n-1)的系数a=1。
●
●
(2)将n格分成2格和n-2格,计算f(n-2)的系数b。
右端2格的铺法有两种(b)。
由图可见,(b)的铺法包含在(a)的铺法中,而(c)的铺法不同于(a),因此f(n-2)的系数b=1。
(3)将n格分成3格和n-3格,计算f(n-3)的系数c。
右端3格的铺法有两种(图4.4.5)。
由图可见,(d)(e)(f)的铺法都可以归结到1格或2格中去,只有1*3的铺法(g)属于新的,因此f(n-3)的系数c=1。
将n格分成n-x格和x格(4<=x<=n-4)的情况都是重复的,因此不再讨论。
由此得出:
f
(1)=1,f
(2)=2,f(3)=4,
f(n)=f(n-1)+f(n-2)+f(n-3) (n>=5)
程序:
varn:
integer;
functionf(i:
integer):
longint;
begin
ifiin[1..2]
thenf:
=i
elseifi=3
thenf:
=4
elsef:
=f(I-1)+f(I-2)+f(I-3);
end;
begin
readln(n);writeln(f(n));
end.
“铺砖问题”有推广价值。
例如某人走n级的楼梯,每步可以走1级、2级或3级,走完n级楼梯共有多少种走法。
这个问题的数学意义和解法与“铺砖问题”相同。
例3:
划分问题
设s是一个具有n个元素的集合s={a1,a2,…an},现将s集合划分成k个满足下列条件的子集合s1,s2,s3。
。
。
。
;
1、si<>空;
2、si∩sj=空;
3、s1∪s2∪s3….∪sn=s (1<=i,j<=k,i<>j)
则称s1,s2…sn是集合s的一个划分,它相当于把集合s中的n个元素放入k个无标号的盒子中,使得没有一个盒子为空,试确定n个元素的集合放入k个无标号盒的划分数s(n,k)
【算法分析】:
例如S={1,2,3,4},k=3。
细心的读者稍加分析后,不难得出S有6种不同的划分方案,即划分数为6。
其方案为{1,2}∪{3}∪{4} {1,3}∪{2}∪{4} {1,4}∪{2}∪{3} {2,3}∪{1}∪{4} {2,4}∪{1}∪{3} {3,4}∪{1}∪{2}
如果对于任意的S集合和k值,就不能凭籍直觉和经验计算划分数和枚举划分方案了。
必须总结出一个数学规律:
设n个元素a1…an放入k个无标号盒的划分数为S(n,k)。
在配置过程中,
有两种情况:
1.设{an}是k个子集中的一个子集,于是把{a1…an-1}划分为k-1子集有S(n-1,k-1)个划分数;
2.如果{an}不是k个子集中的一个,即an必与其它的元素构成一个子集。
首先把{a1,…,an-1}划分成k个子集,这共有S(n-1,k)种划分方式。
然后 再把an加入到k个子集中的一个子集中去,这有k种加入方式。
对于每一种加入方式,都使集合划分为k个子集,因此由乘法原理知,划分数共有k·s(n-1,k)。
从上面的两种情况分析得出 S(n,k)=S(n-1,k-1)+k·S(n-1,k) (n>1,k≥1)
下面,我们来确定s(n,k)的边界条件:
1.我们不可能把n个元素不放进任何一个集合中去,即s(n,0)=0;也不可能在不允许空盒的情况下把n个元素放进多于n的k个集合中去,即k>n时S(n,k)=0。
2.把n个元素放进一个集合或把n个元素放进n个集合,方式数显然是1。
即
S(n,1)=1
S(n,n)=1
显然,通过上述分析可得出划分数S(n,k)的递归关系式:
S(n,k)=S(n-1,k-1)+k·S(n-1,k) (n〉k,k≥1)
S(n,k)=0 (n S(n,k)=1 (k=1)或(k=n)
按照划分数S(n,k)的递归定义,可以直接写出它的递归函数S(n,k)
functionS(n,k:
integer)ofword;
begin
if(k=0)or(k>n)then s:
=0
elseif(k=1)or(k=n)then s:
=1
else s:
=s(n-1,k-1)+k*s(n-1,k);
end;
相似题:
m个相同的球放进n个相同的盒子(可以一个盒子放多个)有多少种不同的放法?
m个相同的球放进n个不相同的盒子(可以一个盒子放多个)有多少种不同的放法?
例4、简单的背包问题。
设有一个背包,可以放入的重量为s。
现有n件物品,重量分别为t1,t2,t3…ti…tn,ti(1≤i≤n),均为正整数。
从n件物品中挑选若干件,使得放入背包的重量之和正好为s
【输入样例】 【输出样例】
thenumberofobject:
5 number:
1weight:
1
totalweight=10 number:
3weight:
2
16275 number:
4weight:
7
【分析】尝试构造函数snap(s,n)解决本题,分析情况如下:
①、先取最后一个物品tn放入背包中,若tn=s,刚好放入包中,问题得到解决并输出(n,tn).
②、若tn>s,则不能放入包中,还得继续挑选;若还剩物品(即n>1),问题即为从剩余的n-1件物品中选取若干个,使得他们的重量和等于s,即snap(s,n)—>snap(s,n-1).
③、若tn1),那么问题即可转换为从剩下的n-1件物品中选取若干件,使得他们的重量和等于包里剩下的可放入重量(s-tn),即:
snap(s,n)—>snap(s,n-1);而选中的tn还要看snap(s-tn,n-1)是否有解,无解的话说明先取tn不合适,就要放弃tn,在剩余的物品中开始挑选,即有snap(s,n)—>snap(s,n-1).
【参考程序】
Constm=10;
vart:
array[1..m]ofinteger;
x,y,i:
integer;f:
boolean;
functionsng(x:
integer):
integer;{判断s-t[n]的三种情况}
begin
ifx>0thensng:
=1
elseifx=0thensng:
=0
elsesng:
=-1;
end;
functionsnap(s,n:
integer):
boolean;{判断是否有解}
begin
casesng(s-t[n])of
0:
beginwriteln(‘number:
’n:
4,‘weight:
’,t[n]:
4);snap:
=true;end;
1:
beginifn>1thenifsnap(s-t[n],n-1)=truethen
beginwriteln(‘number:
’n:
4,‘weight:
’,t[n]:
4); snap:
=true;end
elsesnap:
=snap(s,n-1)
elsesnap:
=false
end;
-1:
ifn>1thensnap:
=snap(s,n-1)elsesnap:
=false
end;
end;
begin
writeln(‘thenumberofobject:
’);readln(y);
writeln(‘totalweight=’);readln(x);
fori:
=1toydoread(t[i]);readln;{数据输入}
f:
=snap(x,y); {x:
weight,y:
number}
ifnot(f)thenwriteln(‘notfound’);
end.
例5、输出n个元素的无重复的全排列。
N个元素有n!
种不同排列。
分析:
1个元素直接输出;
2个元素有两种排列,如(ab)(ba);
3个元素以(abc)为例,有:
abc
acb
bac
bca
cba
cab
分析这些排列,一个简单的算法是:
(1)a后随(bc)的所有排列
(2)b后随(ac)的所有排列
(3)c后随(ba)的所有排列
上面
(2)是将
(1)中的a、b互换位置;上面(3)是将
(1)中的a、c互换位置。
这里意味着可以用循环的方法来重复执行“交换位置,后随剩余序列的所有的排列”;而对剩余序列可以再使用这个方法,这就成了递归调用,后随的元素没有时,就到了递归的边界。
对于n个元素a=(a1a2…ak…an),设过程prem(a,k,n)是求a的第k到n个元素的全排列,设swap(a,k,I)是将a的第k个元素和第i个元素对换,i=k,…,n。
programexp1_7;
vara:
string;k,n:
integer;
procedureswap(vara:
string;k,i:
integer);
vart:
char;
begin t:
=a[k];a[k]:
=a[i];a[i]:
=t;
end;
procedureperm(a:
string;k,n:
integer);
vari:
integer;
begin
ifk=nthenwriteln(a)
elsefori:
=ktondo begin swap(a,k,i);perm(a,k+1,n); end
end;
begin
readln(a);n:
=length(a);perm(a,1,n);
end.
例6、2的幂次方表示
任何一个正整数都可以用2的幂次方表示.例如:
137=2^7+2^3+2^0。
同时约定次方用括号来表示,即a^b可表示为a(b)。
由此可知,137可表示为:
2(7)+2(3)+2(0),进一步:
7=2^2+2+2^0 (2^1用2表示);3=2+2^0;所以最后137可表示为:
2(2
(2)+2+2(0))+2(2+2(0))+2(0)。
又如:
1315=2^10+2^8+2^5+2+1;所以1315最后可表示:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2
(2)+2(0))+2+2(0)
输入:
正整数(n≤20000)
输出:
符合约定的n的0,2表示(在表示中不能有空格)
【分析】:
递归法
2的幂次方由高幂向低幂分解
1. 计算最接近n(小于n)的2的次幂e
2e≤n<2e+1 设x=2e。
2.从e开始按照次幂递减的方向分解(0≤i≤e)设x1—项数。
若n≥x,则分解出x=2i的2幂次方表示:
⑴若x1>0(非第一项),则输出’+’;
⑵分析次幂i
i=0,输出2(0); {递归边界}
i=1,输出2;
i>1,输出2(i的2幂次方表示) {递归}
⑶准备分解下一项
x1←x1+1;n←n-x;
x←xdiv2;(无论n是否大于等于n)
由此得出算法:
proceduresolve(n:
word);
var e,i,x1:
byte;
x:
word;
begin
x←16384;e←14; {214<20000<215}
whilex>ndo {计算最接近n(小于n)的x=2e}
begin x←xdiv2;e←e-1 end;{while}
x1←0;
fori←edownto0do {逐位分解2的幂次i}
begin
ifn≥x thenbegin {若能分解x=2i}
ifx1>0thenwrite(’+’); {若当前是中间项}
caseiof {根据次幂i分解x的2幂次方表示}
0:
write(’2(0)’);
1:
write
(2);
elsebegin
write(’2(’);
solve(i);
write(’)’)
end{else}
end;{case}
x1←x1+1;n←n-x {准备分解下一项x=2i-1}
end;{then}
x←xdiv2
end{for}
end;{solve}
例7、求数字的乘积根。
一个正整数的数字的乘积N的定义是:
这个整数中非零数字的乘积。
例如,整数999的数字乘积为9*9*9,即729。
729的数字乘积为7*2*9,即126。
126的数字乘积为1*2*6,即12。
12的数字乘积为1*2,即2。
一个正整数的数字乘积根N是这样得到的:
反复取该整数的数字乘积,直到得到一位数字为止。
例如,在上面的例子中数字的乘积根是2。
编写一个程序,输入一个正整数(长度不超过200位数字),输出计算其数字乘积根的每一步结果。
【分析】:
每一步得到的乘积根比它的上一步的位数要小,显然存在递归过程,
递归结束的条件是乘积根的位数等于1。
具体程序编码如下:
programex5_17;
varst:
string;
procedureinit;
beginwriteln(‘pleaseinput:
’);readln(st);end;
proceduremake(ss:
string);
vara,b:
array[1..200]ofinteger;
i,j,x,code:
integer;w:
string;
begin
fillchar(a,sizeof(a),0);
fori:
=1tolength(ss)do {取每一位数}
val(ss[i],a[i],code);
fillchar(b,sizeof(b),0);
x:
=1;b[1]:
=1;
fori:
=1tolength(ss)do {每一位非0的数相乘}
begin
forj:
=1toxdo
ifa[i]<>0thenb[j]:
=b[j]*a[i];
forj:
=1toxdo {处理进位}
begin b[j+1]:
=b[j+1]+b[j]div10;
b[j]:
=b[j]mod10;
end;
ifb[x+1]>0thenx:
=x+1;
end;
ss:
=‘’;
fori:
=xdownto1do
begin
str(b[i],w);ss:
=ss+w;
end;
writeln(ss);
iflength(ss)>1thenmake(ss);
end;
begin
init;{输入数据}
writeln(st);
make(st);{递归处理}
readln;
end.
例8、输入N个字符,然后以倒序输出(用递归实现)。
Programdigui(input,output);
Constn=8;
Typemat=array[1..n]ofchar;
Vari:
integer;a:
mat;
Procedureprint(i:
integer);
begin
Ifi=nthenwrite(a[i])
elsebeginprint(i+1);write(a[i]);end;
end;
Begin
fori:
=1tondoreadln(a[i]);
i:
=1; print(i); readln;
End.
例9、猴子选大王:
有n只猴子选大王,先从头到尾1至3报数,报到3的猴子退出,报至尾后,再从尾到头1至3报数,报到3的猴子退出…依次类推,当剩下两只猴子时,报1的为大王。
问若想当大王,应站在什么位置。
【分析】:
十只猴子1-10编号,则出圈的次序为
猴子编号:
1 2 3 4 5 6 7 8 9 10
出圈次序:
3 6 9 7 2 5 4 10 剩下8和1时,8号猴子报1为大王
程序如下:
programhouzi(input,output);
typearr=array[1..100]ofinteger;
varh:
arr;I,j,m,n:
integer;
procedurenum(vara:
arr;varn,i:
integer;m:
integer);
var f,k,s:
integer;
begin
ifn>=3then begins:
=0;k:
=imod2;{k为1表示从头到尾,k为0表示从尾到头}
casekof
1:
forf:
=1tomdo {正向报数}
ifa[f]<>0then begin s:
=s+1;
ifs=3thenbegins:
=0;n:
=n-1;a[f]:
=0;end;
end;
0:
forf:
=mdownto1do{反向报数}
ifa[f]<>0then begin s:
=s+1;
ifs=3thenbegins:
=0;n:
=n-1;a[f]:
=0;end;
end;
end;
i:
=i+1;num(a,n,i,m);{递归调用}