高中信息技术 全国青少年奥林匹克联赛教案 枚举法.docx
《高中信息技术 全国青少年奥林匹克联赛教案 枚举法.docx》由会员分享,可在线阅读,更多相关《高中信息技术 全国青少年奥林匹克联赛教案 枚举法.docx(30页珍藏版)》请在冰豆网上搜索。
高中信息技术全国青少年奥林匹克联赛教案枚举法
2019-2020年高中信息技术全国青少年奥林匹克联赛教案枚举法
枚举法,常常称之为穷举法,是指从可能的集合中一一枚举各个元素,用题目给定的约束条件判定哪些是无用的,哪些是有用的。
能使命题成立者,即为问题的解。
采用枚举算法解题的基本思路:
(1)确定枚举对象、枚举范围和判定条件;
(2)一一枚举可能的解,验证是否是问题的解
下面我们就从枚举算法的的优化、枚举对象的选择以及判定条件的确定,这三个方面来探讨如何用枚举法解题。
枚举算法应用
例1:
百钱买百鸡问题:
有一个人有一百块钱,打算买一百只鸡。
到市场一看,大鸡三块钱一只,小鸡一块钱三只,不大不小的鸡两块钱一只。
现在,请你编一程序,帮他计划一下,怎么样买法,才能刚好用一百块钱买一百只鸡?
算法分析:
此题很显然是用枚举法,我们以三种鸡的个数为枚举对象(分别设为x,y,z),以三种鸡的总数(x+y+z)和买鸡用去的钱的总数(x*3+y*2+z)为判定条件,穷举各种鸡的个数。
下面是解这个百鸡问题的程序
varx,y,z:
integer;
begin
forx:
=0to100do
fory:
=0to100do
forz:
=0to100do{枚举所有可能的解}
if(x+y+z=100)and(x*3+y*2+zdiv3=100)and(zmod3=0)thenwriteln('x=',x,'y=',y,'z=',z);{验证可能的解,并输出符合题目要求的解}
end.
上面的条件还有优化的空间,三种鸡的和是固定的,我们只要枚举二种鸡(x,y),第三种鸡就可以根据约束条件求得(z=100-x-y),这样就缩小了枚举范围,请看下面的程序:
varx,y,z:
integer;
begin
forx:
=0to100do
fory:
=0to100-xdo
begin
z:
=100-x-y;
if(x*3+y*2+zdiv3=100)and(zmod3=0)thenwriteln('x=',x,'y=',y,'z=',z);
end;
end.
未经优化的程序循环了1013次,时间复杂度为O(n3);优化后的程序只循环了(102*101/2)次,时间复杂度为O(n2)。
从上面的对比可以看出,对于枚举算法,加强约束条件,缩小枚举的范围,是程序优化的主要考虑方向。
在枚举算法中,枚举对象的选择也是非常重要的,它直接影响着算法的时间复杂度,选择适当的枚举对象可以获得更高的效率。
如下例:
例2、将1,2...9共9个数分成三组,分别组成三个三位数,且使这三个三位数构成1:
2:
3的比例,试求出所有满足条件的三个三位数.
例如:
三个三位数192,384,576满足以上条件.(NOIPxxpj)
算法分析:
这是xx年全国分区联赛普及组试题(简称NOIPxxpj,以下同)。
此题数据规模不大,可以进行枚举,如果我们不加思地以每一个数位为枚举对象,一位一位地去枚举:
fora:
=1to9do
forb:
=1to9do
………
fori:
=1to9do
这样下去,枚举次数就有99次,如果我们分别设三个数为x,2x,3x,以x为枚举对象,穷举的范围就减少为93,在细节上再进一步优化,枚举范围就更少了。
程序如下:
var
t,x:
integer;
s,st:
string;
c:
char;
begin
forx:
=123to321do{枚举所有可能的解}
begin
t:
=0;
str(x,st);{把整数x转化为字符串,存放在st中}
str(x*2,s);st:
=st+s;
str(x*3,s);st:
=st+s;
forc:
='1'to'9'do{枚举9个字符,判断是否都在st中}
ifpos(c,st)<>0theninc(t)elsebreak;{如果不在st中,则退出循环}
ift=9thenwriteln(x,'',x*2,'',x*3);
end;
end.
在枚举法解题中,判定条件的确定也是很重要的,如果约束条件不对或者不全面,就穷举不出正确的结果, 我们再看看下面的例子。
例3一元三次方程求解(noipxxtg)
问题描述有形如:
ax3+bx2+cx+d=0这样的一个一元三次方程。
给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。
要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
提示:
记方程f(x)=0,若存在2个数x1和x2,且x1样例
输入:
1-5-420
输出:
-2.002.005.00
算法分析:
由题目的提示很符合二分法求解的原理,所以此题可以用二分法。
用二分法解题相对于枚举法来说很要复杂很多。
此题是否能用枚举法求解呢?
再分析一下题目,根的范围在-100到100之间,结果只要保留两位小数,我们不妨将根的值域扩大100倍(-10000<=x<=10000),再以根为枚举对象,枚举范围是-10000到10000,用原方程式进行一一验证,找出方程的解。
有的同学在比赛中是这样做
var
k:
integer;
a,b,c,d,x:
real;
begin
read(a,b,c,d);
fork:
=-10000to10000do
begin
x:
=k/100;
ifa*x*x*x+b*x*x+c*x+d=0thenwrite(x:
0:
2,'');
end;
end.
用这种方法,很快就可以把程序编出来,再将样例数据代入测试也是对的,等成绩下来才发现这题没有全对,只得了一半的分。
这种解法为什么是错的呢?
错在哪里?
前面的分析好象也没错啊,难道这题不能用枚举法做吗?
看到这里大家可能有点迷惑了。
在上面的解法中,枚举范围和枚举对象都没有错,而是在验证枚举结果时,判定条件用错了。
因为要保留二位小数,所以求出来的解不一定是方程的精确根,再代入ax3+bx2+cx+d中,所得的结果也就不一定等于0,因此用原方程ax3+bx2+cx+d=0作为判断条件是不准确的。
我们换一个角度来思考问题,设f(x)=ax3+bx2+cx+d,若x为方程的根,则根据提示可知,必有f(x-0.005)*(x+0.005)<0,如果我们以此为枚举判定条件,问题就逆刃而解。
另外,如果f(x-0.005)=0,哪么就说明x-0.005是方程的根,这时根据四舍5入,方程的根也为x。
所以我们用(f(x-0.005)*f(x+0.005)<0)和(f(x-0.005)=0)作为判定条件。
为了程序设计的方便,我们设计一个函数f(x)计算ax3+bx2+cx+d的值,程序如下:
{$N+}
var
k:
integer;
a,b,c,d,x:
extended;
functionf(x:
extended):
extended;{计算ax3+bx2+cx+d的值}
begin
f:
=((a*x+b)*x+c)*x+d;
end;
begin
read(a,b,c,d);
fork:
=-10000to10000do
begin
x:
=k/100;
if(f(x-0.005)*f(x+0.005)<0)or(f(x-0.005)=0)thenwrite(x:
0:
2,'');{若x两端的函数值异号或x-0.005刚好是方程的根,则确定x为方程的根}
end;
end.
用枚举法解题的最大的缺点是运算量比较大,解题效率不高,如果枚举范围太大(一般以不超过两百万次为限),在时间上就难以承受。
但枚举算法的思路简单,程序编写和调试方便,比赛时也容易想到,在竞赛中,时间是有限的,我们竞赛的最终目标就是求出问题解,因此,如果题目的规模不是很大,在规定的时间与空间限制内能够求出解,那么我们最好是采用枚举法,而不需太在意是否还有更快的算法,这样可以使你有更多的时间去解答其他难题。
2019-2020年高中信息技术全国青少年奥林匹克联赛教案枚举法二
课题:
枚举法
目标:
知识目标:
枚举算法的本质和应用
能力目标:
枚举算法的应用!
重点:
利用枚举算法解决实际问题
难点:
枚举算法的次数确定
板书示意:
1)简单枚举(例7、例8、例9)
2)利用枚举解决逻辑判断问题(例10、例11)
3)枚举解决竞赛问题(例12、例13、例14)
授课过程:
所谓枚举法,指的是从可能的解集合中一一枚举各元素,用题目给定的检验条件判定哪些是无用的,哪些是有用的.能使命题成立,即为其解。
一般思路:
●对命题建立正确的数学模型;
●根据命题确定的数学模型中各变量的变化范围(即可能解的范围);
●利用循环语句、条件判断语句逐步求解或证明;
枚举法的特点是算法简单,但有时运算量大。
对于可能确定解的值域又一时找不到其他更好的算法时可以采用枚举法。
例7:
求满足表达式A+B=C的所有整数解,其中A,B,C为1~3之间的整数。
分析:
本题非常简单,即枚举所有情况,符合表达式即可。
算法如下:
forA:
=1to3do
forB:
=1to3do
forC:
=1to3do
ifA+B=Cthen
Writeln(A,‘+’,B,‘=’,C);
上例采用的就是枚举法。
所谓枚举法,指的是从可能的解的集合中一一枚举各元素,用题目给定的检验条件判定哪些是无用的,哪些是有用的。
能使命题成立的,即为解。
从枚举法的定义可以看出,枚举法本质上属于搜索。
但与隐式图的搜索有所区别,在采用枚举法求解的问题时,必须满足两个条件:
1预先确定解的个数n;
2对每个解变量A1,A2,……,An的取值,其变化范围需预先确定
A1∈{X11,……,X1p}
……
Ai∈{Xi1,……,Xiq}
……
An∈{Xn1,……,Xnk}
例7中的解变量有3个:
A,B,C。
其中
A解变量值的可能取值范围A∈{1,2,3}
B解变量值的可能取值范围B∈{1,2,3}
C解变量值的可能取值范围C∈{1,2,3}
则问题的可能解有27个
(A,B,C)∈{(1,1,1),(1,1,2),(1,1,3),
(1,2,1),(1,2,2),(1,2,3),
………………………………
(3,3,1),(3,3,2),(3,3,3)}
在上述可能解集合中,满足题目给定的检验条件的解元素,即为问题的解。
如果我们无法预先确定解的个数或各解的值域,则不能用枚举,只能采用搜索等算法求解。
由于回溯法在搜索每个可能解的枚举次数一般不止一次,因此,对于同样规模的问题,回溯算法要比枚举法时间复杂度稍高。
例8给定一个二元一次方程aX+bY=c。
从键盘输入a,b,c的数值,求X在[0,100],Y在[0,100]范围内的所有整数解。
分析:
要求方程的在一个范围内的解,只要对这个范围内的所有整数点进行枚举,看这些点是否满足方程即可。
参考程序:
programexam8;
var
a,b,c:
integer;
x,y:
integer;
begin
write('Inputa,b,c:
');readln(a,b,c);
forx:
=0to100do
fory:
=0to100do
ifa*x+b*y=cthenwriteln(x,'',y);
end.
从上例可以看出,所谓枚举法,指的是从可能的解集合中一一枚举各元素,用题目给定的检验条件判定哪些是无用的,哪些是有用的.能使命题成立,即为其解。
例9巧妙填数
1
9
2
3
8
4
5
7
6
将1~9这九个数字填入九个空格中。
每一横行的三个数字组成一个三位数。
如果要使第二行的三位数是第一行的两倍,第三行的三位数是第一行的三倍,应怎样填数。
如图6:
图6
分析:
本题目有9个格子,要求填数,如果不考虑问题给出的条件,共有9!
=362880种方案,在这些方案中符合问题条件的即为解。
因此可以采用枚举法。
但仔细分析问题,显然第一行的数不会超过400,实际上只要确定第一行的数就可以根据条件算出其他两行的数了。
这样仅需枚举400次。
因此设计参考程序:
programexam9;
var
i,j,k,s:
integer;
functionsum(s:
integer):
integer;
begin
sum:
=sdiv100+sdiv10mod10+smod10
end;
functionmul(s:
integer):
longint;
begin
mul:
=(sdiv100)*(sdiv10mod10)*(smod10)
end;
begin
fori:
=1to3do
forj:
=1to9do
ifj<>ithenfork:
=1to9do
if(k<>j)and(k<>i)thenbegin
s:
=i*100+j*10+k;{求第一行数}
if3*s<1000then
if(sum(s)+sum(2*s)+sum(3*s)=45)and
(mul(s)*mul(2*s)*mul(3*s)=362880)then{满足条件,并数字都由1~9组成}
begin
writeln(s);
writeln(2*s);
writeln(3*s);
writeln;
end;
end;
end.
例10在某次数学竞赛中,A、B、C、D、E五名学生被取为前五名。
请据下列说法判断出他们的具体名次,即谁是第几名?
条件1:
你如果认为A,B,C,D,E就是这些人的第一至第五名的名次排列,便大错。
因为:
没猜对任何一个优胜者的名次。
也没猜对任何一对名次相邻的学生。
条件2:
你如果按D,A,E,C,B来排列五人名次的话,其结果是:
说对了其中两个人的名次。
还猜中了两对名次相邻的学生的名次顺序。
分析:
本题是一个逻辑判断题,一般的逻辑判断题都采用枚举法进行解决。
5个人的名次分别可以有5!
=120种排列可能,因为120比较小,因此我们对每种情况进行枚举,然后根据条件判断哪些符合问题的要求。
根据已知条件,A<>1,B<>2,C<>3,D<>4,E<>5,因此排除了一种可能性,只有4!
=24种情况了。
参考程序:
ProgramExam10;
Var
A,B,C,D,E:
Integer;
Cr:
Array[1..5]OfChar;
Begin
ForA:
=1To5Do
ForB:
=1To5Do
ForC:
=1To5Do
ForD:
=1To5Do
ForE:
=1To5DoBegin
{ABCDE没猜对一个人的名次}
If(A=1)Or(B=2)Or(C=3)Or(D=4)Or(E=5)ThenContinue;
If[A,B,C,D,E]<>[1,2,3,4,5]ThenContinue;{他们名次互不重复}
{DAECB猜对了两个人的名次}
IfOrd(A=2)+Ord(B=5)+Ord(C=4)+Ord(D=1)+Ord(E=3)<>2ThenContinue;
{ABCDE没猜对一对相邻名次}
If(B=A+1)Or(C=B+1)Or(D=C+1)Or(E=D+1)ThenContinue;
{DAECB猜对了两对相邻人名次}
IfOrd(A=D+1)+Ord(E=A+1)+Ord(C=E+1)+Ord(B=C+1)<>2ThenContinue;
Cr[A]:
='A';Cr[B]:
='B';Cr[C]:
='C';
Cr[D]:
='D';Cr[E]:
='E';
Writeln(Cr[1],'',Cr[2],'',Cr[3],'',Cr[4],'',Cr[5]);
End;
End.
例11:
来自不同国家的四位留学生A,B,C,D在一起交谈,他们只会中、英、法、日四种语言中的2种,情况是,没有人既会日语又会法语;A会日语,但D不会,A和D能互相交谈,B不会英语,但A和C交谈时却要B当翻译,B,C,D三个想互相交谈,但找不到共同的语言,只有一种语言3人都会,编程确定A,B,C,D四位留学生各会哪两种语言。
分析:
将中、法、日、英四种语言分别定义为CHN、FRH、JPN、ENG,则四种语言中取两种共有(CHN,ENG),(CHN,FRH),(CHN,JPN),(ENG,FRH),(ENG,JPN),(FRH,JPN)六种组合,分别定义为1、2、3、4、5、6。
据已知,没有人既会日语又会法语;因此,组合6不会出现;A会日语,所以A只可能等于3、5;D不会日语,所以D只可能等于1、2、4;B不会英语,所以B只可能等于2、3;见下表。
如果我们对A、B、C、D分别进行枚举,根据判定条件,即可找到答案。
(CHN,ENG)
(CHN,FRH)
(CHN,JPN)
(ENG,FRH)
(ENG,JPN)
A
×
×
×
B
×
×
×
C
D
×
×
程序如下:
programEXAM11;
type
Language=(CHN,ENG,FRH,JPN);
TNoSet=setofLanguage;
const
No:
array[1..5]ofTNoSet=
([CHN,ENG],[CHN,FRH],[CHN,JPN],[ENG,FRH],[ENG,JPN]);
var
A,B,C,D:
1..5;
Can1,Can2,Can3,Can4:
Boolean;
functionMight(Lang:
Language):
Boolean;
var
Bool:
Boolean;
begin
Bool:
=false;
ifNo[A]*No[A]*No[C]=[Lang]thenBool:
=True;
ifNo[A]*No[B]*No[D]=[Lang]thenBool:
=True;
ifNo[A]*No[C]*No[D]=[Lang]thenBool:
=True;
ifNo[B]*No[C]*No[D]=[Lang]thenBool:
=True;
Might:
=Bool
end;
procedurePrint(A,B,C,D:
Integer);
procedureShow(P:
Integer;Ch:
Char);
var
I:
Integer;
Lang:
Language;
begin
Write(ch,':
');
forLang:
=CHNtoJPNdo
ifLanginNo[P]then
caseLangof
CHN:
Write('CHN':
5);
FRH:
Write('FRH':
5);
JPN:
Write('JPN':
5);
ENG:
Write('ENG':
5);
end;
Writeln;
end;
begin
Show(A,'A');
Show(B,'B');
Show(C,'C');
Show(D,'D');
end;
begin
forA:
=3to5do
ifA<>4thenforB:
=2to3do
forC:
=1to5do
forD:
=1to4doifD<>3thenbegin
{A和D能互相交谈}
Can1:
=No[A]*No[D]<>[];
{A和C交谈时却要B当翻译}
Can2:
=(No[A]*No[C]=[])and(No[A]*No[B]<>[])
and(No[B]*No[C]<>[]);
{B,C,D三个想互相交谈,但找不到共同的语言}
Can3:
=No[B]*No[C]*No[D]=[];
{只有一种语言3人都会}
Can4:
=Ord(Might(CHN))+Ord(Might(ENG))
+Ord(Might(FRH))+Ord(Might(JPN))=1;
ifCan1andCan2andCan3andCan4thenPrint(A,B,C,D);
end;
end.
例12古纸残篇
在一位数学家的藏书中夹有一张古旧的纸片。
纸片上的字早已模糊不清了,只留下曾经写过字的痕迹,依稀还可以看出它是一个乘法算式,如图7所示。
这个算式上原来的数字是什么呢?
夹着这张纸片的书页上,“素数”两个字被醒目的划了出来。
难道说,这个算式与素数有什么关系吗?
有人对此作了深入的研究,果然发现这个算式中的每一个数字都是
***
×**
****
****
*****
图7
素数,而且这样的算式是唯一的。
请你也研究一番,并把这个算式写出来。
分析:
实际上,只要知道乘数和被乘数就可以写出乘法算式,所以我们可以枚举乘数与被乘数的每一位。
然后再判断是不是满足条件即可。
计算量是45=1024,对于计算机来说,计算量非常小。
参考程序:
ProgramExam12;
Const
Su:
Array[1..4]OfLongint=(2,3,5,7);
Var
A1,A2,A3,B1,B2,X,Y,S:
Longint;
FunctionKx(S:
Longint):
Boolean;{判断一个数
是不是都是由素数组成}
Begin
Kx:
=True;
WhileS<>0DoBegin
IfNot((SMod10)In[2,3,5,7])ThenBegin
Kx:
=False;
Exit;
End;
S:
=SDiv10;
End;
End;
Begin
ForA1:
=1To4Do
ForA2:
=1To4Do
ForA3:
=1To4Do
ForB1:
=1To4Do
ForB2:
=1To4DoBegin
X:
=Su[A1]*100+Su[A2]*10+Su[A3];{X为被乘数}
IfX*Su[B1]<1000ThenContinue;
IfX*Su[B2]<1000ThenContinue;
IfX*(Su[B1]*10+