高中信息技术 全国青少年奥林匹克联赛教案 枚举法二文档格式.docx
《高中信息技术 全国青少年奥林匹克联赛教案 枚举法二文档格式.docx》由会员分享,可在线阅读,更多相关《高中信息技术 全国青少年奥林匹克联赛教案 枚举法二文档格式.docx(21页珍藏版)》请在冰豆网上搜索。
(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:
begin
write('
Inputa,b,c:
'
);
readln(a,b,c);
forx:
=0to100do
fory:
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;
i,j,k,s:
functionsum(s:
integer):
sum:
=sdiv100+sdiv10mod10+smod10
end;
functionmul(s:
longint;
mul:
=(sdiv100)*(sdiv10mod10)*(smod10)
fori:
=1to3do
forj:
=1to9do
ifj<
>
ithenfork:
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;
例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:
ForC:
ForD:
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)<
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;
Can
1,Can2,Can3,Can4:
Boolean;
functionMight(Lang:
Language):
Bool:
=false;
ifNo[A]*No[A]*No[C]=[Lang]thenBool:
=True;
ifNo[A]*No[B]*No[D]=[Lang]thenBool:
ifNo[A]*No[C]*No[D]=[Lang]thenBool:
ifNo[B]*No[C]*No[D]=[Lang]thenBool:
Might:
=Bool
procedurePrint(A,B,C,D:
Integer);
procedureShow(P:
Integer;
Ch:
Char);
I:
Lang:
Language;
Write(ch,'
forLang:
=CHNtoJPNdo
ifLanginNo[P]then
caseLangof
CHN:
Write('
CHN'
5);
FRH:
FRH'
JPN:
JPN'
ENG:
ENG'
Writeln;
Show(A,'
Show(B,'
Show(C,'
Show(D,'
forA:
=3to5do
ifA<
4thenforB:
=2to3do
=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.
例12古纸残篇
在一位数学家的藏书
中夹有一张古旧的纸片。
纸片上的字早已模糊不清了,只留下曾经写过字的痕迹,依稀还可以看出它是一个乘法算式,如图7所示。
这个算式上原来的数字是什么呢?
夹着这张纸片的书页上,“素数”两个字被醒目的划了出来。
难道说,这个算式与素数有什么关系吗?
有人对此作了深入的研究,果然发现这个算式中的每一个数字都是
***
×
**
****
****
*****
图7
素数,而且这样的算式是唯一的。
请你也研究一番,并把这个算式写出来。
实际上,只要知道乘数和被乘数就可以写出乘法算式,所以我们可以枚举乘数与被乘数的每一位。
然后再判断是不是满足条件即可。
计算量是45=1024,对于计算机来说,计算量非常小。
ProgramExam12;
Const
Su:
Array[1..4]OfLongint=(2,3,5,7);
A1,A2,A3,B1,B2,X,Y,S:
Longint;
FunctionKx(S:
Longint):
Boolean;
{判断一个数
是不是都是由素数组成}
Kx:
=True;
WhileS<
0DoBegin
IfNot((SMod10)In[2,3,5,7])ThenBegin
=False;
Exit;
S:
=SDiv10;
End;
ForA1:
=1To4Do
ForA2:
ForA3:
ForB1:
ForB2:
=1To4DoBegin
X:
=Su[A1]*100+Su[A2]*10+Su[A3];
{X为被乘数}
IfX*Su[B1]<
1000ThenContinue;
IfX*Su[B2]<
IfX*(Su[B1]*10+Su[B2])<
10000ThenContinue;
{它们分别是两个四位数,一个五位数}
If(Kx(X*Su[B1])=False)Or
(Kx(X*Su[B2])=False)Or
(Kx(X*(Su[B1]*10+Su[B2]))=False)ThenContinue;
{满足其他数都是由质数构成}
Writeln('
Su[A1],Su[A2],Su[A3]);
*'
Su[B1],Su[B2]);
-----------'
X*Su[B2]);
X*Su[B1]);
X*(Su[B1]*10+Su[B2]));
例13:
时钟问题(IOI94-4)
在图8所示的3*3矩阵中有9个时钟,我们的目标是旋转时钟指针,使所有时钟的指针都指向12点。
允许旋转时钟指针的方法有9种,每一种移动用一个数字号(1,2,…,9)表示。
图2-11示出9个数字号与相应的受控制的时钟,这些时钟在图中以灰色标出,其指针将顺时针旋转90度。
输入数据:
由输入文件INPUT.TXT读9个数码,这些数码给出了9个时钟时针的初始位置。
数码与时刻的对应关系为:
0——12点
1——3点
2——6点
3——9点
图2-11中的例子对应下列输入数据:
330
222
212
输出数据:
将一个最短的移动序列(数字序列)写入输出文件OUTPUT.TXT中,该序列要使所有的时钟指针指向12点,若有等价的多个解,仅需给出其中一个。
在我们的例子中,相应的OUTPUT.TXT的内容为:
5849
输入输出示例:
INPUT.TXT
OUTPUT.TXT
5489
具体的移动
方案如图10所示。
首先,我们分析一下表示时钟时针初始位置的数码j(0≦j≦3)与时刻的对应关系:
每移动一次,时针将顺时针旋转90度。
由此我们可以得出:
对于任意一个时钟i(1≦i≦9)来说,从初始位置j出发至少需要Ci=(4-j)mod4次操作,才能使得时针指向12点。
而对每种移动方法要么不采用,要么采用1次、2次或3次,因为操作四次以后,时钟将重复以前状态。
因此,9种旋转方案最多产生49个状态。
移动方案选取与顺序无关。
样例中,最佳移动序列为5849,同样4589序列也可达到目标。
因此,求解过程中可以直接选取序列中从小至大排列的移动序列即可。
设pi表示第i种旋转方法的使用次数(0≦pi≦3,1≦i≦9)。
则可能的解的集合为{P1,P2,……,P9},该集合共含49个状态。
从图2.11中,我们可以分析出9个时钟分别被哪些方法所控制,见下表:
时钟号
控制时钟方案
检验条件
1
1、2、4
C1=(P1+P2+P4)mod4
2
1、2、3、5
C2=(P1+P2+P3+P5)mod4
3
2、3、6
C3=(P2+P3+P6)mod4
4
1、4、5、7
C4=(P1+P4+P5+P7)mod4
5
1、3、5、7、9
C5=(P1+P3+P5+P7+P9)mod4
6
3、5、6、9
C6=(P3+P5+P6+P9)m
od4
7
4、7、8
C7=(P4+P7+P8)mod4
8
5、7、8、9
C8=(P5+P7+P8+P9)mod4
9
6、8、9
C9=(P6+P8+P9)mod4
因此我们可以设计如下枚举算法:
forp1:
=0to3do
forp2:
.........
forp9:
ifc1满足时钟1andc2满足时钟2and...andc9满足时钟9then
打印解路径;
显然,上述枚举算法枚举了所有49=262144个状态,运算量和运行时间颇大。
我们可以采取缩小可能解范围的局部枚举法,仅枚举第1、2、3种旋转方法可能取的43个状态,根据这三种旋转方法的当前状态值,由下述公式
P4=order(C1-P1-P2);
P5=order(C2-P1-P2-P3);
P6=order(C3-P2-P3);
P7=order(C4-P1-P4-P5);
P8=order(C8-P5-PP9);
P9=order(C6-P3-P5-P6);
得出其余P4……P9的相应状态值。
然后将P1,P2,…,P9代入下述三个检验条件
一一枚举,以求得确切解。
由此可见,上述局部枚举的方法(枚举状态数为43个)比较全部枚举的方法(枚举状态数为49个)来说,由于大幅度削减了枚举量,减少运算次数,因此其时效显著改善,是一种优化了的算法。
programIOI94_4;
Inp='
input.txt'
Outp='
output.txt'
Clock,Map:
array[1..3,1..3]ofInteger;
{Clock:
第((I+2)mod3)*3+J个时钟从初始时间到12点的最少移动次数}
{Map:
最短移动序列中,数字((I+2)mod3)*3+J的次数}
procedureInit;
I,J:
Assign(Input,Inp);
Reset(Input);
forI:
=1to3do
{读入9个时钟指针的初始位置,求出每个时钟从初始到12点的最少移动次数}
forJ:
=1to3dobegin
Read(Clock[I,J]);
Clock[I,J]:
=(4-Clock[I,J])mod4;
Close(Input);
functionOrder(K:
Integer):
c:
c:
=k;
whilec<
0doinc(c,4);
whilec>
4thendec(c,4);
Order:
=k;
procedureMain;
{计算和输出最短移动序列}
I,J,K:
{枚举最短移动序列中数字1、2、3的个数可能取的43种状态}
Assign(Output,Outp);
Rewrite(Output);
forMap[1,1]:
=0to3do
forMap[1,2]:
forMap[1,3]:
=0to3dobegin
Map[2,1]:
=Order(Clock[1,1]-Map[1,1]-Map[1,2]);
Map[2,3]:
=Order(Clock[1,3]-Map[1,2]-Map[1,3]);
Map[2,2]:
=Order(Clock[1,2]-Map[1,1]-Map[1,2]-Map[1,3]);
Map[3,1]:
=Order(Clock[2,1]-Map[1,1]-Map[2,1]-Map[2,2]);
Map[3,3]:
=Order(Clock[2,3]-Map[1,3]-Map[2,2]-Map[2,3]);
Map[3,2]:
=Order(Clock[3,2]-Map[3,1]-Map[3,3]-Map[2,2]);
{根据数字1、2、3个数的当前值,得出数字4~9的个数}
if((Map[2,1]+Map[3,1]+Map[3,2])mod4=Clock[3,1])and
((Map[2,3]+Map[3,2