NOIP初赛复习算法1.docx
《NOIP初赛复习算法1.docx》由会员分享,可在线阅读,更多相关《NOIP初赛复习算法1.docx(91页珍藏版)》请在冰豆网上搜索。
NOIP初赛复习算法1
OK备战NOIP2010提高组初赛复习
——算法篇之算法设计的常用策略
程序设计主要包括两个方面
●结构特性的设计(数据结构的设计);
●行为特性的设计(算法设计);
第一篇主要阐述了结构特性的设计,即如何为解题选择合适的数据结构。
但这只是问题的一个方面。
接下来的问题是如何将解题过程的每一个细节准确地加以定义,并用某种语言完整地描述出来。
这一过程既所谓行为特性的设计,亦称算法设计。
第二篇将围绕这个主题展开讨论。
算法是一组(有限个)规则,它为某个特定问题提供了解决问题的运算序列。
通俗点说,就是计算机解题的过程。
在这个过程中,无论是形成解题思路还是编写程序,都是在实施某种算法。
前者是推理实现的算法,后者是操作实现的算法。
计算机解题的核心是算法设计。
一个算法应该具有以下五个重要特征:
(1)有穷性一个算法必须保证执行有限步之后结束;
(2)确切性算法的每一步骤必须确切定义;
(3)输入一个算法有0个或多个输入,以刻划运算对象的初始情况。
所谓0个输入是指算法本身定出了初始条件;
(4)输出一个算法有一个或多个输出,以反映对输入数据加工后的结果。
没有输出的算法是毫无意义的;
(5)能行性算法原则上能够精确地进行,而且人们用笔和纸做有穷次即可完成;
下面,我们对构成算法所依据的一些基本方法和思路展开分析。
对于读者来说,为了获得一个即有效又优美的算法,必须了解一些基本的常用的算法设计思路。
现实世界的事物多姿多彩,千变万化。
我们不可能规定一些简单的条条框框和套用现成的模式去解决所有的问题。
然而,现实世界也有大量事物存在着许多相似或相近的规律,存在本质相同的东西。
正因为如此,就有可能形成一些常用的方法思路(策略),按照这些方法思路分析和求解试题,一般可使解题过程变得容易一些。
本章将介绍几种典型的算法策略,这些策略常用于那些需要独立思考、见解独创和有所创新的非标准题。
当然面对实际试题,究竟使用哪种方法,怎样用,需要“具体问题具体分析”,需要机智灵活,不宜死记成规、生搬硬套,得靠自己想方设法。
所谓“阵而后战,兵法之常,运用之妙,存乎一心”是也。
§5.1对应的策略
将问题A对应另一个便于思考或有求解方法的问题B,化繁为简,变未知为已知。
一一对应技术是一种重要的策略。
它的思维核心是求同,通过举一反三、触类旁通地对已经解决的类似问题和有关事实作联想,外推出事物间的联系,从而全面、深入地认识和分析事物。
“世界上没有完全不同的东西”,相似点的普遍存在为我们使用对应策略解题提供了基础。
但是对应并不等于等价,它不仅要分析问题间的共同点,还要分析问题间的不同点,是一种异中求同的思维方法。
一、对应精典问题
“客观世界物质第一性,思维第二性”。
经典问题及其算法的知识积累是解题的基础。
竞赛的许多试题最终都可以转化为经典问题,因此必须尽可能多地掌握经典问题及其算法的知识。
解题时,心中经常要回忆已经解决的经典问题和有关解法,往往会收到意想不到的效果。
当然这些试题并不直接以经典问题的原貌出现。
我们必须合理运用求同思维、求异思维比较两者间的相同点和不同点,通过适当的方法将试题转化为经典问题。
【例题廿】换车问题
一个城市有n个车站,已知m条连接这些车站的公共汽车单向路线。
求站1至站n的最少换车数。
输入:
nm
以下m行依次列出每条线路的车站序号。
输出:
最少换车次数。
算法分析
这个问题对我们来讲并非陌生。
如果将问题要求改为“求站1到站n最少经过多少站”,就变为我们相当熟悉的最短路径的典型应用。
即
令有向图G=,|V|=n。
若vi,vj∈V且(Vi,Vj)∈E当且仅当i站、j站在某条线路上相邻,(Vi,Vj)的权Wij设为1。
显然,汽车线路经过的站数=路径顶点数=路径边数+1=路径的权和+1。
为使总站数最少,只要使路径的权和最小,即只要求出图G中Vi至Vj的最短路径即可。
但现在的问题是求最少换车次数,虽然它与求经过最少站数的总是有共同背景,但要求不同。
我们化异为同,重新对原图G作了修正:
若Vi,Vj∈V且(Vi,Vj)∈E当且仅当站i与站j依次在同一条公共汽车线路上(Vi可直达Vj),Wij仍为1。
然后运用求最短路径方法计算V1至Vn的最短路径长度,其长度-1即为最少换车次数。
设
t—当前线路的车站数;
oneline—当前线路的车站序列;
g—有向图的相邻矩阵;
我们按照下述算法计算最少换车次数:
fillchar(g,sizeof(g),0);{有向图初始化}
fori:
=1tomdo{读入每条线路的信息,构造g图}
begin
t←0;fillchar(oneline,sizeof(oneline),0);{第i条线路初始化}
whilenoteolndo
begin
t←t+1;读第i条线路的第t个车站序号oneline[t];
forj:
=1tot-1dog[oneline[j],oneline[t]]←1;
end;{while}
end;{for}
求g图中V1至Vn的最短路径长度dist[n];
输出站1至站n的最少换车次数dist[n]-1;
由上可见,对应策略使用得如何,即与其掌握经典算法的多少和理解的透彻程度有关,也与其应用经典算法于实际的实践能力有关,更与其拓延经典算法应用范围的创造力息息相关。
下例的解题过程将更清楚地说明这一点。
【例题廿一】寻找数列
寻找一个由整数组成的数列,其中任意连续p个整数之和为正,任意连续q个整数之和为负。
若不存在这样的整数数列,则输出NO;否则输出其中一个数列。
算法分析
本题算法设计的关键在于连续之和的表示。
如果我们就事论事,直接将第i+1个整数ai+1开始的k个整数之和描述成多项式ai+1+ai+2+…+ai+k的话,算法就很难建立了。
为此,我们暂且撇去每一项数究竟为何值的具体细节,而将注意力集中至连续性这一特点上。
设
si—数列前i个整数之和,即si=a1+a2+…+ai。
其中s0=0(0≤i≤n)。
显然根据题意
sisi+q由上述两组不等式引出有向图g,图中共有n+1个顶点,分别为s0…sn。
若si>sj(0≤i,j≤n),则从si往sj引出一条有向边。
如图(n=6,p=5,q=3)
构造有向图g的过程如下:
fillchar(g,sizeof(g),0);{有向图g的相邻矩阵初始化}
fillchar(d,sizeof(d),0);{各顶点的入度序列初始化}
fori:
=0tondo{根据两组不等式构造有向图g}
begin
ifi+p≤nthenbeging[i+p,i]←1;d[i]←d[i]+1;end;{then}
ifi+q≤nthenbeging[i,i+q]←1;d[i+q]←d[i+q]+1;end;{then}
end;{for}
显然按照上述定义,如果图g可以拓扑排序,其顶点的拓扑序列可以对应满足条件的整数数列;反之,不存在这样的整数数列:
对图g进行拓扑排序;
if图g有回路then无解退出
else生成拓扑序列order[0]…order[n];
如果图g可以拓扑排序,则按照下述方法将拓扑order转换成s数组:
拓扑序列中顶点对应的s值是递减的,其中s0=0。
如果order[i]=0,则依次设定
sorder[0]=i,sorder[1]=i-1,……,sorder[i-1]=1,sorder[i]=0,sorder[i+1]=-1,……,sorder[n]=i-n
例如,对于(图5.1—2)中的有向图g,可以计算出拓扑序列
i:
0123456
i
0
1
2
3
4
5
6
order[i]
2
5
0
3
6
1
4
sorder[i]
2
1
0
-1
-2
-3
-4
即s0=0,s1=-3,s2=2,s3=-1,s4=-4,s5=1,s6=-2
根据s的定义,可得出
ai=(a0+a1+…+ai-1+ai)-(a0+a1+…+ai-1)=si-si-1
即
a1=s1-s0=-3a2=s2-s1=5a3=s3-s2=-3
a4=s4-s3=-3a5=s5-s4=5a6=s6-s5=-3
显然,在这个整数数列中,任意连续5个整数之和为正,任意连续3个整数之和为负。
由拓扑序列构造整数数列的方法如下:
fillchar(s,sizeof(s),0);
fori:
=0tondo
iforder[i]<>0thenforj:
=idownto0dos[order[j]]←s[order[j]]+1
elsebreak;
forj:
=i+1tondos[order[j]]←i-j;
fori:
=1tondoa[i]←s[i]-s[i-1];
从形式上看,这道数列计算题似乎与图论风马牛不相干,题中即未出现图论中常见的“车站”,“城市”等顶点,也未出现“公路”,“铁路”等边,更未出现“长度”,“传输时间”等到权,但算法设计者却明白“它山之石可以攻玉”的道理,使用图论中的经典算法—拓扑排序漂漂亮亮地解决了该题。
他独辟蹊径,把si作为顶点,s的大小关系作为边,并按照其递减顺序设定边的权值,其构思的巧妙令人叹服。
正是这种创造力在解题过程中起了关键性的作用。
二、对应简单问题
有些试题的求解方法原本很简单,但由于其表现形式陌生,因此窄看感到棘手。
但只要你果断地抓住主要矛盾,舍弃与目标无关的次要信息,去粗存精,去伪存真,由此及彼,由表及里,便可以返朴归真,将试题与一个简单的问题对应起来。
【例题廿二】删数问题
键盘输入一个高精度的正整数,去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。
编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小。
输入:
n
s
输出:
顺序列出s个被删去的数字
算法分析
由于正整数n的有效数位为240位,因此采用string类型存贮n。
为了尽可能逼近目标,我们每一步总是选择一个使剩下的数最小的数删去:
按高位→低位的顺序搜索,若各位数字递增,则删最大的尾数符;否则删第一个递减区间的首字符,这样便形成了一个新数串。
然后回到串首,按上述规则删下一个数符。
依次类推,直至删除s个数符为止。
例如
n=‘178543’s=4
删数过程如下
n=‘178543’删‘8’
‘17543’删‘7’
‘1543’删‘5’
‘143’删‘4’
‘13’
这样删数问题与如何寻找递减区间首字符这样一个简单的问题对应起来:
whiles>0do{按贪心策略删s个数}
begin
i←1;
while(idelete(n,i,1);s←s-1;
end;{while}
while(length(n)>1)and(n[1]=‘0’)dodelete(n,1,1);{删去串首的无用零}
输出n;
【例题廿三】取数游戏
给出2*n个自然数。
游戏双方分别为A方(计算机方)和B方(对奕的人)。
只允许从数列两头取数。
A先取,然后双方依次轮流取数。
取完时,谁取得的数字总和最大为取胜方;双方和相等,属于A胜。
试问A方可否有必胜的策略
输入:
2*n个自然数
输出:
前2*n行为游戏经过。
每两行分别为A方所取的数和B方所取的数,其中B方取数时应给予提示,让游戏者选择取哪一头的数(L/R—左端或右端)。
最后一行分别为A方取得的数和和B方取得的数和。
算法分析
设自然数列
79364253
我们设计一种方案,从数大的一头让A、B轮流取两个连续数:
由此得出
A方取得的数和为7+3+4+5=19
B方取得的数和为9+6+2+3=20
按照规则,判定A方输。
虽然A方败给B方,但是我们却发现了一个有趣的事实:
A方取走偶位置的数后,剩下两端数都处于奇位置;反之,若A方取走奇位置的数后,剩下两端数都处于偶位置。
即无论B方如何取法,A方即可以取走奇位置的所有数,亦可以取走偶位置的所有数。
由此萌发一种策略:
让A方取走数和较大的奇(或偶)位置上的所有数,则A方必胜。
这样,取数问题便对应于一个简单问题—让A方取奇偶位置中数和较大的一半数。
设
j—A取数的奇偶位置标志
SA,SB—A方取数和,B方取数和(SA≥SB)
a—2*n个自然数序列;
lp,rp—序列的左端位置和右端位置;
ch—B方取数的位置信息(’L’或’R’);
SA,SB←0;{计算A方取数和、B方取数和,A方取数的位置标志}
fori:
=1tondo
begin
SA←SA+a[2*i-1];SB←SB+a[2*i];
end;{for}
ifSA≥SBthenj←1
elsebegin
j←0;SA
SB;
end;{else}
lp←1;rp←2*n;
fori:
=1tondo{A方和B方依次进行n次对奕}
begin
if((j=1)and(lpmod2=1))or((j=0)and(lpmod2=0))
{若A方应取奇位置数且左端位置为奇,或者A方应取偶位置数且左端位置为偶,则A方取走左端位置的数}
thenbegin
输出A方取a[lp];lp←lp+1;
end{then}
elsebegin{否则A方取走右端位置的数}
输出A方取a[rp];rp←rp-1;
end;{else}
输出提示信息:
B方的取数位置(L/R);
repeat
读ch;
ifch=‘L’thenbegin输出B方取a[lp];lp←lp+1;end;{then}
ifch=‘R’thenbegin输出B方取a[rp];rp←rp-1;end;{then}
untilch
{‘L’,’R’};
end;{for}
输出A方取数的和SA和B方取数的和SB;
三、对应数学问题
运用已学到的数学知识,对试题给出的各个对象及其关系进行分析、演绎、归纳,从而建立一个清晰简练的解析式,使得试题与某个数学问题对应。
当然,这个数学问题的计算量非人工演算所能及,必须译成算法语言,通过计算机运算方可完成。
【例题廿四】极值问题
m、n为整数,且满足下列两个条件:
①m、n∈{1,2,…,k}
②(n2-m*n-m2)2=1
编一程序,由键盘输入K,求一组满足上述两个条件的m、n,并且使m2+n2的值最大。
例如,若K=1995,则m=987,n=1597,则m、n满足条件,且可使m2+n2的值最大。
输入:
键盘输入正整数K(1≤k≤109)。
输出:
m
n
算法分析
这是一门典型的数学题。
如果读者仅从初等数学的角度考虑,通常会得出这样一种算法:
由条件②(n2-m*n-m2)2=1得出
n2-m*n-m2+1=0
n2-m*n-m2-1=0
根据求根公式
n1=(m+△1)/2n2=(m+△2)/2
n3=(m-△1)/2n4=(m-△2)/2
其中:
△1=
;△2=
;
下面再来考虑条件①。
由于n>1,因此排除了n3和n4存在的可能性,即
n=n1=(m+△1)/2或者n=n2=(m+△2)/2
又由于n和m是整数,因此△1和△2应为整数。
同样,(m+△1)/2和(m+△2)/2也应为整数。
有了上述条件限制和m与n的函数关系式,使得求m2+n2值最大的一组m和n就比较方便了。
由于m2+n2单调递增,因此我们从m=k出发,按递减方向将m值代入n的求根公式。
只要△1(或△2)为整数、n1(或n2)为整数且小于k,则得出的一组m和n一定使m2+n2的值最大。
算法如下:
m←k;
whilem≥1do
begin
△1←
;
if△1为整数then
begin
n1←(m+△1)/2;
if(n1为整数)and(n1≤k)thenbegin输出m和n1;halt;end;{then}
end;{then}
△2←
;
if△2为整数then
begin
n2←(m+△2)/2;
if(n2为整数)and(n2≤k)thenbegin输出m和n2;halt;end;{then}
end;{then}
m←m-l;
end;{while}
上述算法从数学意义上讲是一定可以出得出正确解的。
但是采用该算法的读者疏漏了一个重要条件──1≤k≤109。
一般说,如果K值超过105,上述算法决计不可能在竞赛限定的15秒内出解。
要提高算法效率,不能不借助一些组合数学的知识和组合分析的方法解题:
首先,我们对表达式(n2-m*n-m2)2=1作一些数学变换
(n2-m*n-m2)2
=(m2+n*m-n2〕2
=((n+m)2-n*(n+m)-n2〕2
=〔(n')2-m'*n'-(m')2〕2
其中n'=m+n,m'=n
由上述数学变换式可以得出,如果m和n为一组满足条件①和条件②的解,那么m'和n'也是一组满足条件①和条件②的解。
另外
m=1n=1
满足条件①和条件②,因此,我们可以将所有满足条件①和条件②的m和n按递增顺序排成一个Fibomacci数列{1,1,2,3,5,8,……},数列中小于k的最大两个相邻数即为试题所要求的一组m和n。
计算过程如下:
m←1;n←1;{确定Fibonacci数列的头两项数}
Repeat{顺推数列中小于K的最大两个相邻数}
t←m+n;
ift≤kThenBeginm←n;n←t;End;{then}
Untilt>k;
输出m和n;
显然,将极值问题与Fibonacci数列问题对应起来,可使得计算过程简捷许多。
【例题廿五】安排赛事
有n(n为偶数)个球队要进行友谊赛,要求每一队必须和所有其它队交锋一次。
在安排赛事时为了照顾参赛队的休息,一天赛事中同一球队不允许进行两场比赛。
问要在最短天数内安排完比赛,该如何设计程序。
输入:
n
输出:
最短天数k
以下k行为每天交锋的两队序号
算法分析
我们每天设
场次,让每个队出场一次,显然,为了使每个队与其余队交锋一次,比赛天数定为n-1。
这样的安排即不违反规则,又能在最短时间内安排完比赛。
例如n=4时,每天有
=2场,共进行n-1=3天比赛:
第一天1队与2队3队与4队
第二天1队与3队2队与4队
第三天1队与4队2队与3队
设第i天的比赛为x队对奕y队,x、y与i有如下对应关系:
(x+y)mod(n-1)=i
表明第i天的一场比赛为
x:
y或者y:
x。
第i天交锋的一支球队x可依次枚举为0,…,n-2。
若x球队的序号一旦确定,则与之交锋的球队y按下式计算:
y=((n-1)+i-x)mod(n-1)
若y=0,则设定y=n-1;若x=y,则设定y=n或者x=n,使得对垒阵局为n:
x或者x:
n。
例如n=4时,同余表如下
yx
1234
I=0(第一天)
I=2(第二天)
I=3(第三天)
2143
3412
4321
通过上述方法,我们将赛事安排与一个同余表问题对应。
设同余表A,其中
A[i,j]—第i天与j队交锋的球队序号(0≤i≤n-1,1≤j≤n)。
同余表的计算过程如下:
fori:
=0ton-2do{依次计算每一天的比赛场次}
forj:
=1tondo{枚举x队的序号}
begin
k←((n-1)+i-j)mod(n-1);{计算第i+1天与x队交锋的y队序号}
ifk=0thenk←n-1;
A[i,j]←k;
ifA[i,j]=jthenbeginA[i,j]←n;A[i,n]←j;end;{then}
end;{for}
在同余表中,同一天里出现了x:
y和y:
x的对垒阵局,它们同属一个场次。
我们不妨取x场互不相交的比赛:
fori:
=0ton-2do
begin
输出第i+1天的比赛提示;
forj:
=1tondoifjend;{for}
【例题廿六】排序
排序是计算机学科中一个常见任务,有一种特殊的排序,最多只有三个关键字。
例如,试图对这次竞赛的奖牌榜排序时,就只有3个关键字,所有的金牌获得者在最前面,随后是银牌获得者,最后是铜牌获得者。
本题中用1,2,3分别表示3个关键字,需将它们按升序排列,排序是通过一系列对换操作实现的。
一次对换操作可以交换两个数的位置。
子任务a:
请写一个程序,对于一个给定的只含有关键字的序列,计算最少需要几次对换操作就可以将其按升序排列。
子任务b:
输出一种最少次数的对换方案。
输入:
文件名INPUT.TXT,第一行是序列长度N(1≤N≤1000),随后N行每一行有一个关键字。
输出:
文件名OUTPUT.TXT,第一行是你的程序计算出的所需最少次数L(子任务a)。
随后L行是具体的操作方案(子任务B),每一行有两个数P和q(以一个数分隔),表示此次操作将第P个数据和第q个数据交换。
输入输出示例:
下面给出了一个输入文件名和一个可能的输出文件。
INPUT.TXT
OUTPUT.TXT
9
2
2
1
3
3
3
2
3
1
4
13
47
92
59
算法分析
1、计算目标序列
将原序列按升序排列,这个排序后的序列称之为目标序列。
目标序列中的每一个元素值称之为所在位置的目标状态。
设
f—原序列;m—目标序列;p[i]—数i在原序列的频率;
我们按照下述方法计算目标序列:
fillchar(p,sizeof(p),0);
fori:
=1tondo{计算每个数的频率}
begin
读原序列的第i个数f[i];p[f[i]]←p[f[i]]+1;
end;{for}
fori:
=1top[1]doM[i]←1;{计算目标序列}
fori:
=p[1]+1top[1]+p[2]doM[i]←2;
fori:
=p[1]+p[2]+1tondoM[i]←3;
2、计算最少对换次数
由于序列中元素的关键字仅有1,2,3三个,因此对换情况无非六种
1→22→1;
2→33→2;
3→11→3;
每一种对换i→j(1≤i,j≤3,i<>j)用数对(i,j)表示。
我