怎样学算法呀?

minkerui 2001-07-22 05:55:53
这几天学算法把头都学晕了!昨天递归,今天学回溯,明天又有动态规划,深度搜索与广度搜索……
让我根本不能理解,各位说说,算法到底应该怎么学啊?
...全文
216 15 打赏 收藏 转发到动态 举报
写回复
用AI写文章
15 条回复
切换为时间正序
请发表友善的回复…
发表回复
woodqiang 2001-07-27
  • 打赏
  • 举报
回复
须然水平有限还不理想它们的关系但有一篇这样的文章(我还没有看过的,但题目倒是有点关系):


数学解题思维在程序设计中的应用
浙江省镇海中学 夏方方

【关键字】 数学 思维 程序设计
【摘 要】 信息学竞赛解题与数学常有着密切的联系。本文从对两者各自的思维特点的分析与比较入手,提出数学解题思维在计算机程序设计中有着广泛应用的观点,并就其适用面结合实例逐一进行了论述。

一、引论
数学是基础学科中的基础,它几乎与所有的自然科学学科有着密不可分的关系。信息学,作为一门新兴的学科,与数学的关系更是尤为密切。解答信息学竞赛题所需用到的基础知识如数论、数理逻辑、集合论、组合数学、图论、几何学等无不来自于数学;而算法设计时所需做出的时空复杂度的计算、贪心法的优劣判断、动态算法的最优性证明等等也都依赖于数学思维。因此,从广义上说,解答信息学竞赛题所需用的正是数学思维。然而,信息学作为一门独立的学科毕竟有其特殊之处,这也使得程序设计中使用的数学思维与解答数学奥林匹克竞赛题中使用的数学思维呈现出了显著的差异性。本文的主旨正是通过对这种差异性进行分析来讨论如何在程序设计中借鉴一些解答数学题的经验。

二、本论
1.数学解题思维与计算机解题思维的差异性
先看一个古老的例题:
A,B,C三人互为邻居,他们养有不同的宠物:狗(D),白鹭(E),狐狸(F);抽不同品牌的香烟:万宝路(X),红塔(Y),三五(Z)。已知:(1) A不养狗;B不养狐狸 (2) 养狗的人不抽三五 (3) 养白鹭的人抽万宝路 (4) B不抽红塔。 编程输出三人所养的宠物和抽烟情况。
面对这个题目,有的人会直接上机,不假思索地采用穷举法——遍举所有情形并筛选出符合题意的解,一蹴而就,得到下面的程序:
[程序1_1]
Program Example_1;
Var
Ap,Bp,Cp : char; { A,B,C三人所养宠物 }
Ac,Bc,Cc : char; { A,B,C三人各自的烟品牌 }
Begin
for Ap:='D' to 'F' do
for Bp:='D' to 'F' do
for Cp:='D' to 'F' do
for Ac:='X' to 'Z' do
for Bc:='X' to 'Z' do
for Cc:='X' to 'Z' do
if ([Ap]+[Bp]+[Cp]=['D'..'F'])and([Ac]+[Bc]+[Cc]=['X'..'Z'])
and(Ap<>'D')and(Bp<>'F')
and(not( (Bp='D')and(Bc='Z') or (Cp='D')and(Cc='Z') ))
and( (Ap='E')and(Ac='X') or (Bp='E')and(Bc='X') or (Cp='E')and(Cc='X'))
and(Bc<>'Y')
then
begin
writeln('A: ',Ap,' ',Ac);
writeln('B: ',Bp,' ',Bc);
writeln('C: ',Cp,' ',Cc);
end;
End.
  也有人会用自己的数学思维稍作分析,在程序1_1的基础上做出若干改进,删去一些显见的无解情形(如当A,B两人的宠物相同时,就无须枚举C的宠物和三人的抽烟情况,因为这种情形决不可能出解),从而把相对较轻的工作量留给计算机,得到程序如下:
[程序1_2]
Program Example_2;
Var
Ap,Bp,Cp : char;
Ac,Bc,Cc : char;
Begin
for Ap:='E' to 'F' do
for Bp:='D' to 'E' do if Ap<>Bp then
for Ac:='X' to 'Z' do
for Bc:='X' to 'Z' do if (Ac<>Bc)and(Bc<>'Y') then
begin
Cp:=chr(3*ord('E')-ord(Ap)-ord(Bp));
Cc:=chr(3*ord('Y')-ord(Ac)-ord(Bc));
if (not( (Bp='D')and(Bc='Z') or (Cp='D')and(Cc='Z') ))
and( (Ap='E')and(Ac='X') or (Bp='E')and(Bc='X') or (Cp='E')and(Cc='X'))
then
begin
writeln('A: ',Ap,' ',Ac);
writeln('B: ',Bp,' ',Bc);
writeln('C: ',Cp,' ',Cc);
end;
end;
End.
  然而,还有人采用了第三种程序:
[程序1_3]
Program Example_3;
Begin
writeln('A: F Z');
writeln('B: E X');
writeln('C: D Y');
End.
如果说程序1_1是纯粹的计算机式的,程序2更突出其电脑人脑相结合的特点的话,那么程序3则多少显得有些近乎旁门,它已被优化得太彻底以至于几乎不能再被称为程序了。显然,它是被当做一道数学题求解了:
先从B养宠物情况找到突破口,由题设条件(1)B不养狐狸,则B只能养狗或白鹭。
而若B养狗,由条件(2)(3)(4)可分别推知,B不抽三五、万宝路和红塔。这样一来,B无烟可抽,与题设矛盾,应舍去。
所以,B只能养白鹭,再由(1),则A养狐狸,C养狗。
宠物情况明了后,各人的烟牌也可迅速推出:B抽万宝路;C抽红塔;A抽三五。
经过这样彻底的分析,留给计算机的任务只能是结论输出了。当然,由于程序1_3的运行结果与前两个程序完全一致,它也是可以被接受的。
以上只是一个很极端的例子,我们当然不会奢望如今的竞赛题出现这样的题目,但是这种数学解题思维介入程序设计的行为无疑是颇具启发性意味的。
数学解题的思维的特点在于人需要依赖自身的智慧对已知条件做出分析,得到一些初步的结论,然后在这些结论的基础上一步步地导向最终结论。解数学题时面对的已知条件往往是固定的,在解题过程中,对采用的方法存在一种默认的限制即只可以实施一些情形不多的分类讨论,而任何理论上可行但非常耗时的枚举则被认为是不可接受的。而用计算机编程解题的特点在于面对的已知条件通常不是确定的,人需要编制的是一种能处理一类已知条件的推理运算规则,然后依赖计算机予以实施。由于计算机有着超人的计算速度,因此一些数学解题中并不适用的算法在计算机的帮助下往往又是可行的。然而,长期的计算机常用算法的训练容易使我们产生一种思维上的惰性(不妨称之为计算机解题思维),从而可能会无意识地放弃对问题本质的深入思考,而过分依赖于计算机的强大功能,一次又一次地采用搜索等具有明显“机械”气质的方法。但是,当我们有机会进入更高档次的训练或比赛后,我们在那屏幕前的一大片“超时”前清醒了,原来计算机的能力也不过如此!毕竟,高难度的竞赛的考察重点不在于选手对某个死板的算法的熟练程度抑或是对号入座的本领。事实上,如今的信息学竞赛题在时间、空间上的限制常常很苛刻,使得程序编制者不能采取听任计算机“蛮干”的态度,相反要为它做一些“力所能及”的工作。而各人能力的差异往往正是由这种贡献的大小程度决定的。如果人通过并不复杂的推理归纳先导出一些初步的结论,在此基础上再编制算法往往能使程序的质量产生飞跃。
2.数学解题思维在解决信息学竞赛题中的应用
(1) 一些明显由数学题改编而来的信息学竞赛题常常应考虑数学题解法。
  不可否认,数学题是信息学竞赛题的一大题源。下面要举的例题与前苏联80年代的一道十一年级数学联赛证明题颇有相似之处。
[极值问题]--NOI'95
m,n为整数,且满足下列两个条件:
① m,n∈1,2,……,k (1≤k≤109)
② (n2-mn-m2)2=1
编一程序,由键盘输入k,求一组满足上述两个条件的m,n,并且使m2+n2的值最大。
例如,若k=1995,则m=987,n=1597,则m,n满足条件,且使m2+n2的值最大。
[分析]
首先,不管是用数学解法还是用计算机解法,都需要对已知条件②做出分析。
∵ (n2-mn-m2)2=1
∴ n2-mn-m2±1=0
根据求根公式,n1,2=(m+△1,2)/2; n3,4=(m-△1,2)/2
其中:△1=sqrt(5m2+4); △2=sqrt(5m2-4)
再由条件①,n>0,应舍去n3,n4,所以
n=(m+sqrt(5m2+4))/2或(m+sqrt(5m2-4))/2 (*)
因为m,n是整数,因此△1,△2都应为整数。而一旦△为整数,由于△与m奇偶性相同,n必为整数。
分析到此,如果匆匆转入计算机思维,极易得到下面的算法:
由于m2+n2单调递增,因此只需从m=k出发,按递减方向将m值代入n的求根公式,只要△为整数,且得到的相应m,n值不大于k,就是原题的解。对应的程序如下:
[程序2_1]
Program NOI95_4_1;
Var
k,m,n : longint;
delta1,delta2 : real;
Begin
write('k='); readln(k); { 键盘输入k }
m:=k;
while m>=1 do
begin
delta1:=sqrt(5.0*m*m+4);
if (delta1-trunc(delta1))<1e-9 then { 判断△1是否为整数 }
begin
n:=trunc((m+delta1)/2);
if n<=k then begin writeln(m,',',n);halt; end;
end;
delta2:=sqrt(5.0*m*m-4);
if (delta2-trunc(delta2))<1e-9 then { 判断△2是否为整数 }
begin
n:=trunc((m+delta2)/2);
if n<=k then begin writeln(m,',',n);halt; end;
end;
dec(m);
end;
end.
上述算法从理论上讲是可以得到正确解的,但是它忽略了原题中的一个重要条件---1≤k≤109。一旦k值超过106,上述算法决不可能在竞赛限定的15秒内出解。因此,从这个意义上讲,该算法是失败的。
但是,只要我们延续着原先的数学解题思维,不难得到另一种大异其趣的算法:
(m=1,n=1)是方程(n2-mn-m2)2=1极为显见的一组解,再将m=1代入到(*)式,又能发现(m=1,n=2)也是满足方程的一组解,继续试验,又能得到如下几组简单的解:
(m=2,n=3), (m=3,n=5), (m=5,n=8)
由此,猜想Fibonacci数列{1,1,2,3,5,8,13,21,……}的任何两相邻项均满足方程②,即(a2n+1-an+1an-a2n)2=1。用数学归纳法对这个结论进行证明。
证明:
⑴ 当n=1时,an=a1=1,an+1=a2=1,满足。
⑵ 假设n=k时,原命题成立。则有(a2k+1-ak+1ak-a2k)2=1
当n=k+1时,(a2n+1-an+1an+1-a2n)2
= (a2k+2-ak+2ak+1-a2k+1)2
  = ((ak+1+ak)2-(ak+1+ak)ak+1-a2k+1)2
  = (a2k+akak+1-a2k+1)2
  = (a2k+1-ak+1ak-a2k)2
 = 1
由⑴⑵可知,对任意n∈N,原命题均成立。
经过以上分析,建立了坚实的理论基础,原题所要求的一组m,n转化为Fibonacci数列中不大于k的最大两个相邻数。相应的程序如下:
[程序2_2]
Program NOI95_4_2;
Var
k,m,n,t : longint;
Begin
write('k='); readln(k); { 键盘输入k }
m:=1; n:=1; { Fibonacci数列的头两项 }
repeat { 顺推Fibonacci数列中不大于k的最大两个相邻项 }
t:=m+n;
if t<=k then begin
m:=n; n:=t;
end;
until t>k;
writeln(m,',',n); { 输出结论 }
End.
可见,并不复杂的数学分析推理换来了程序编制的省时,而且程序的效率奇高,再也不会因为竞赛的时限而显得捉襟见肘。
在这里,解题过程中强调了几个重要的数学解题思维的运用——试验、类比、猜想和归纳。
与此类似的例子还有实数数列(NOI'94),最优分解方案(IOI'96中国队组队赛试题)等。对于这些本身数学味颇浓的信息学竞赛题,常常需要尝试着将它们当做数学题来解。如果我们完全有能力“解”出一个答案,那么我们还为什么非要把它“搜索”出来呢?即便不能,我们也常可以设法找到解答的数学描述,这时只需再将之转化为程序即可。
(2)一些表面上与数学关系不大的题目,如果能事先采用数学解题的思维进行分析,总结出一些规律,往往能使算法的时间复杂度得到大幅度的降低。
[晚会彩灯]——IOI'98
为使IOI'98的晚宴更具节日气氛,我们安装了一组N个彩灯,编号从1到N。这些灯被连接到4组按钮上:
按钮1: 当该按钮被按下时,所有的彩灯将改变它们的状态:状态为ON的变为OFF;而状态为OFF的则变为ON。
按钮2: 改变所有奇数号灯的状态。
按钮3: 改变所有偶数号灯的状态。
按钮4: 改变编号为3K+1 的灯的状态:(其中,K>=0), 例如, 1,4,7,...
有一计数器C,它记录按钮按下的总的次数。
当晚会开始时,所有的灯是ON状态,且计数器C是0。
任务:给你一个计数器C的值和有些灯最终状态的信息。请写出一个程序,以确定N个灯所有可能的最终状态组合方案,这些灯的状态要与给定的信息相一致,没有重复方案。
输入输出:(略)。
约束条件:10≤N≤100; 1≤C≤10000
[分析]
这是信息学竞赛中经常出现的一种题型,要求列举出所有符合一定要求的方案。
如果不对这道具体的问题进行数学分析而直接上机编程,可能会条件反射到“枚举+判断”的算法:根据C值,穷举所有可能按下按钮的组合和先后序列,再逐个对每种情形进行模拟,将得到的最终情形与测试数据中的限制比较,筛选出满足的方案。即便能对这种算法进行优化,由于受到思维定势的桎梏,常常很难使算法的效率得到本质上的提高。
事实上,原题中计数器C的上限为10000,因此上述算法的拙劣程度是不难想象的。
其实,只要能耐下心来,对各个按钮的数学性质稍做推敲,就不难发现下述的规律:
u 最终各灯的on/off状态仅由各按钮的按下次数决定,而与各按钮的按下次序无关。
u 用*0,*1,*2,*3,*4分别表示不按按钮,仅按下一次按钮1,按钮2,按钮3或按钮4的效果。则:
¨ 同一按钮按下偶数次的效果=*0
¨ 同一按钮按下奇数次的效果=只按下一次该按钮的效果
¨ *1+*2=*3 ; *2+*3=*1 ; *1+*3=*2 ; *1+*2+*3=*0
再进一步分析:
不管总按下次数(C)如何,最终的效果只可能是以下8种简化按钮组合效果之一:
①=*1 ②=*2 ③=*3 ④=*4 ⑤=*1+*4 ⑥=*2+*4 ⑦=*3+*4 ⑧=*0
u 当C=1时,①②③④均可能实现,⑤⑥⑦⑧不可能实现。
u 当C=2时,①②③⑤⑥⑦⑧均可能实现,④不可能实现。
u 当C>=3时,①②③④⑤⑥⑦⑧全部可能实现。
(其中第三点可由数学归纳法得到证明,限于篇幅,本文从略。)
因此程序的任务转化为针对C值,产生所有可能的效果(4种,7种或8种),然后筛选出其中满足末状态限制的情形。
同时,我们发现C的范围限制只是题目的一种装饰,因为无论C值是3还是10000,程序的运行时间、最终结果都决无二致。而N值的大小对本题也不会产生任何实质性的影响。分析到此,可谓看清了题目的本质。程序如下:
[程序3]
Program Party_Lamps;
Const
f1n = 'Party.in'; { 输入文件名 }
f2n = 'Party.out'; { 输出文件名 }
disabled : array[1..2]of set of byte=([5,6,7,8],[4]);
{ disabled[i]---C为i时,无法达到的效果序号 }
divide : array[1..7]of byte=(1,2,2,3,3,6,6);
effect : array[1..7]of set of byte=([0],[1],[0],[1],[0,2],
[3,4,5],[0,1,2]);
{ 第i种效果能使被divied[i]除余数为effect[i]的灯改变状态 }
Var
f1,f2 : text;
fstate : array[1..2,1..3]of integer;
{ fstate[1]---最终要求设为on的灯序号 }
{ fstate[2]---最终要求设为off的灯序号 }
num : array[1..2]of byte;
{ num[1]要求设为on的灯数 }
{ num[2]要求设为off的灯数 }
poss : array[1..8]of boolean; { 8种效果的可能性 }
n,c,i,j : word;
Procedure init;
begin
assign(f1,f1n);reset(f1);
readln(f1,n);readln(f1,c);
fillchar(fstate,sizeof(fstate),0);
fillchar(num,sizeof(num),0);
for i:=1 to 2 do begin
read(f1,fstate[i,num[i]+1]);
while(fstate[i,num[i]+1]<>-1)do begin
inc(num[i]);read(f1,fstate[i,num[i]+1]);
end;
readln(f1);
end;
close(f1);
assign(f2,f2n);rewrite(f2);
end;
Begin
init;
fillchar(poss,sizeof(poss),true); { 开始时,认为8种效果都可能达到 }
if c in [1,2] then { 根据C值对poss进行修正 }
for i:=1 to 8 do
if i in disabled[c] then poss[i]:=false;
for i:=1 to 7 do if poss[i] then { 根据fstate值对poss进行修正 }
begin
for j:=1 to num[1] do
if((fstate[1,j] mod divide[i]) in effect[i])then poss[i]:=false;
if num[2]>0 then poss[8]:=false;
for j:=1 to num[2] do
if not((fstate[2,j] mod divide[i]) in effect[i])then poss[i]:=false;
end;
{ 输出所有可能的效果所导致的最终情形 }
for i:=1 to 7 do if poss[i] then
begin
for j:=1 to n do begin
if((j mod divide[i]) in effect[i])then write(f2,0)
else write(f2,1);
end;
writeln(f2);
end;
if poss[8] then for j:=1 to n do write(f2,1);
close(f2);
End.
与此题类似的还有电子锁(NOI'92),质数方阵(IOI'94),称重(IOI'93中国队组队赛试题)等。解这些题目时,如能先借助一些组合数学、数论的知识推出一些规律,再把这些结论应用于程序,往往能使程序跳过一些机械繁复的步骤,从而使程序效率大为提高。
(3)有些题目,需要先依靠计算机得出一些结论,然后介入人的数学思维,避免无必要的重复运算,使算法变得精简。
[小球钟]--国际大学生竞赛题
时间是运动的一种方式,所以常常用运动来度量时间。例如,小球钟是一个通过不断在轨道上移动小球来度量时间的简单设备。每分钟,一个转动臂将一个小球从小球队列的底部挤走,并将它上升到钟的顶部并将它安置在一个表示分钟,5分钟和小时的轨道上。这里可以显示从1:00到12:59范围内的时间,但无法表示“a.m.”和“p.m.”。若有2个球在分钟轨道,6个球在5分钟轨道及5个球在小时轨道上,就显示时间5:32。
不幸的是,大多数市场上提供的小球钟无法显示日期,尽管只需要简单地加上一些轨道就可以了。当小球通过钟的机械装置被移动后,它们就会改变其初始次序。仔细研究它们随着时间的流逝随之发生的次序的改变,可以发现相同的次序会不断出现。由于小球的初始次序最后迟早会被重复,所以这段时间的长短是可以被度量的,这完全取决于所提供的小球的总数。
小球钟的运作:
  每分钟,最近最少被使用的那个小球从位于球钟底部的小球队列被移走,并将上升并安置于显示分钟的轨道上,这里可以放置4个小球。当第5个小球滚入该轨道,它们的重量使得轨道倾斜,原先在轨道上的4个小球按照与它们原先滚入轨道的次序相反的次序加入到钟底部的小球队列。引起倾斜的第5个小球滚入显示5分钟的轨道。该轨道可以放置11个球。当第12个小球滚入该轨道,它们的重量使得轨道倾斜,原先11个小球同样以相反的次序加入钟底部的小球队列。而这第12个小球滚入了显示小时的轨道。该轨道同样可以放置11个球,但这里有一个外加的固定的不能被移动的小球,这样小时的值域就变为1到12。从5分钟轨道滚入的第12个小球将使小时轨道倾斜,这11个球同样以相反的次序加入钟底部的小球队列,然后那第12个小球同样加入钟底部的小球队列。
输入
  输入定义了一序列的小球时钟。每个时钟都按照前面描述的那样运作。所有时钟的区别仅在于它们在1:00时钟启动时刻小球初始个数的不同。在输入的每行上给出一个时钟的小球数,它并不包括那个在小时轨道上的固定的小球。合法的数据应在27到127之间。0表明输入的结束。
输出
  输出中每一行只有一个数,表示对应的输入情形中给出的小球数量的时钟在经过多少天的运行可以会到它的初始小球序列。
输入范例      输出范例
 30     15
 45      378
 0
[分析]
  这是一道典型的模拟题。题目中花大量的文字不厌其烦地介绍小球钟的运作规则,无非是让程序员能通过编程精确地用计算机将之模拟。粗看之下,本题与数学题相去较远。编程者往往会得到这样的算法:逐分钟地模拟小球钟的运作,1440分钟过去后,即为一天结束,天数累加器加一。如此往复,直至钟底部的小球队列重又回到初始状态时为止。这期间流逝的天数即为球钟的运作周期。定下这个算法后,编程者往往会把主要精力集中到数据结构的选择上,以期找到一种相对易操作而又高效的模拟方法(这常常正是模拟题的测试重点)。以上是在传统的计算机解题思维方式下得出的算法,然而它的适用面是极为狭窄的。一个仅含39个小球的小球钟的运作周期为6000多天,这意味着要完成这个模拟至少需要1440×6000=8.64*106次循环,显然这是不可能在20秒内结束的,因而就更谈不上用这样的程序来解决更复杂的测试数据了。
  显然,我们需要换一种思维方式。对于每个具体的小球钟,小球的总数是给定的,我们就可以试着将之当做一个数学题来解。一开始,我们会觉得无从下手,因为我们遇到了一个困难--对小球钟的具体运作规律缺乏明确的概念也即缺少可供推理分析的已知数学条件。这一点正可依赖于计算机予以解决,不妨让计算机模拟小球钟最先12小时的运行情况,得到半天后的钟底部的新小球队列。有了这个条件后,我们还是一摆脱计算机就无能为力的吗?显然,答案是否定的。这时我们可以在两次的钟底部小球队列间建立起一种一一对应的关系。

初始时,钟底部的小球编号依次是:1, 2, 3, 4, ..., n.
半天后,钟底部的小球编号依次是:X1,X2, X3,X4, ...,Xn.

半天的小球钟运行的最终结果就表现为以下的一组映射:
  1---->X1
 2---->X2
 ......
 n---->Xn
  我们无须对小球钟内部的具体运作规则做太多的探究,只需确认一点:小球钟的运作规则保证了上述映射关系是恒定不变的,就可以脱离小球钟也脱离计算机人为地推测出小球钟运行24小时后,36小时后,……,钟底部的小球队列情况。
  可见,数学解题思维的介入,使得问题得到了抽象,原先以分钟为单位进行的模拟现在得以以12小时为单位进行,效率提高何其之多!
如果我们能执着于这种数学解题思维进行思考,又能使程序得到不少极富成效的优化。
u 每个小球回到它开始时在钟底部的位置所需的周期通常要比整个小球钟的周期小得多。因此,只需算出每个小球各自的周期,然后求其最小公倍数,即为小球钟的周期。
u 对于同处一个映射环的各小球,它们的周期必定是相同的,因此只需计算其中一个的周期,而忽略其它。
通过以上的分析,从原先颇为纯粹的模拟题中衍生出了“映射”、“最小公倍数”等等的数学术语,人为推理的时间确实较前一种方法为长,但算法质量上的天壤之别也是显而易见的。下面的程序在计算一个包含有27~127范围内所有球数的个测试数据时,仅需2秒。
[程序4]
Program Clock;
Const
f1n = 'Clock.in'; { 输入文件名 }
f2n = 'Clock.out'; { 输出文件名 }
Var
f1,f2 : text; { 输入输出文件变量 }
n,i : integer; { n--当前情形的小球总数; i--循环变量 }
m1,m2,m3,m : string; { m--钟底部的小球队列; m1--分钟轨道中的小球队列 }
c,c2 : char; { m2--5分钟轨道中的小球队列; m3--小时轨道中的小球队列 }
now,long : longint; { now--钟底部小球队列会到初始状态所需经历的12小时数 }

Function g(a,b:longint):longint; { 递归求a,b的最大公约数 }
begin
if (b=0) then g:=a
else if (b=1) then g:=1
else g:=g(b,(a mod b)); { 辗转相除 }
end;
Function reverse(s:string):string; { 求串s的反序串 }
var
s2 : string;
begin
s2:='';
while s<>'' do
begin
s2 := s[1]+s2;
delete(s,1,1);
end;
reverse:=s2;
end;
Begin
assign(f1,f1n); reset(f1);
assign(f2,f2n); rewrite(f2); { 初始化输入输出文件 }
readln(f1,n);
while n>0 do
begin
m:='';
for i:=1 to n do m:=m+chr(i);
repeat { 对小球钟第一个12小时内的运作进行模拟 }
c:=m[1];
delete(m,1,1);
if length(m1)<4 then m1:=m1+c
else begin
m:=m+reverse(m1);
m1:='';
if length(m2)<11 then m2:=m2+c
else begin
m:=m+reverse(m2);
m2:='';
if length(m3)<11 then m3:=m3+c
else begin
m:=m+reverse(m3)+c;
m3:='';
end;
end;
end;
until (m1='')and(m2='')and(m3=''); { 三个队列皆空标志12小时结束 }
now:=1;
for i:=1 to length(m) do { 计算出每个小球回到初始位置的周期 }
if m[i]<>#0 then { 所有小球的周期的最小公倍数即为所求 }
begin
c:=m[i];
m[i]:=#0;
long:=1;
while m[ord(c)]<>#0 do
begin
inc(long);
c2:=m[ord(c)];
m[ord(c)]:=#0;{处于同一映射环的小球的周期必相同, 无须重复计算}
c:=c2;
end;
now:=(now*long) div g(now,long); { 将now与long的最小公倍数赋给now }
end;
writeln(f2,now div 2); { 一天包含两个12小时(now必为偶数,这点可以证明) }
readln(f1,n);
end;
close(f1); close(f2); { 关闭文件 }
End.

三、结论
综上所述,常规的计算机解题和数学解题在思维模式上既有着密切的联系但往往也会存在一些差异,而这种差异常常是可以互补的。一些优秀的信息学选手之所以常能编出效率高人一筹的程序,不仅因为他们有着良好的计算机基本功,也是由于他们常能拓宽视野,多角度地思考问题,富有灵感而不囿于传统的计算机算法。如果我们在解答信息学竞赛题时,能多从数学的角度来分析问题,发挥人在猜测、联想、归纳、抽象、构造、找规律等智能思维上的优势,常常能使计算机如虎添翼,针对具体的问题创制出灵活高效的解题策略。

参 考 书 目

耿素云,屈婉玲,张立昂编著,《离散数学》,清华大学出版社,1995年。
u 吴文虎,王建德编著,《青少年国际和全国信息学(计算机)奥林匹克竞赛指导——组合数学的算法与程序设计》,清华大学出版社,1997年。
u 吴文虎,王建德编著,《国际国内信息学(暨计算机)竞赛试题解析(1992~1993)》,电子工业出版社,1994年。
u 吴文虎,王建德编著,《国际国内信息学(计算机)竞赛试题解析(1994~1995)》,清华大学出版社,1997年。
u 吴文虎,王建德编著,《国际国内奥林匹克信息学(计算机)1996年竞赛试题解析》,北京大学出版社,1997年。
u 吴文虎,王建德编著,《实用算法的分析与程序设计》,电子工业出版社,1998年。

CYQ_96 2001-07-24
  • 打赏
  • 举报
回复
天涯何处无算法?世上何处有数学?
woodqiang 2001-07-23
  • 打赏
  • 举报
回复
有些人比赛时要算着时间和空间的复杂度。但也不用很深的数学吧?
woodqiang 2001-07-23
  • 打赏
  • 举报
回复
to qingrun(青润) 我都是你所讲那样学算法的,我也认同你的方法。只是我一直不理解为什么算法和数学挂钩,我学过很多算法和数学都没有直接的关系。
starfish 2001-07-23
  • 打赏
  • 举报
回复
青润说得好,我深表赞同!
青润 2001-07-23
  • 打赏
  • 举报
回复
woodqiang(木头):
实际上数学无处不在,你没有听说过:离开数学,任何科学都不成其为科学。这句话么?这句话是爱因斯坦说的。
算法的本质上就是数学方法的归纳和整理,也就是用数学的观点来看待问题和思考问题。实际上几乎所有的数学只是都可以用计算机语言来描述的。只是复杂程度不大一样而已。
testnet 2001-07-23
  • 打赏
  • 举报
回复
不知你是看哪本,我的哪本都是数学.
yxf 2001-07-23
  • 打赏
  • 举报
回复
learn Discrete Mathematics first
wolfboy 2001-07-23
  • 打赏
  • 举报
回复
要多实践吧,要不都只是理论知识
SoftWare1999 2001-07-22
  • 打赏
  • 举报
回复
I have no time
GIVE UP!
青润 2001-07-22
  • 打赏
  • 举报
回复
学算法,首先看你的数学如何?数学好,学起来回很容易。
其次,就是你的逻辑推理能力和接受能力,其他的都是次要的。

学的过程中,最重要的是能够推理通过,学算法可以不考虑编程实现,但在刚开始的时候要尽力去用程序实现,这样是入门得好办法,但到了较高层次的时候,就可以不考虑了,因为那样会浪费时间,降低学习的效率。
就谈这么多吧。
minkerui 2001-07-22
  • 打赏
  • 举报
回复
But I have no time.
Zig 2001-07-22
  • 打赏
  • 举报
回复
do more execises.
super_online 2001-07-22
  • 打赏
  • 举报
回复
ARE YOU INDEED INSTERESTING IN ARITHMETIC?
IF NOT GIVE UP!
windindance 2001-07-22
  • 打赏
  • 举报
回复
一次学这么多吗?一个一个来吧,贪多吃不烂。

33,006

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧