Tuesday 2007年11月13日
on the way |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Ø 复赛的命题
1. 准循大纲:考察常用的数据结构和基本算法;
2. 考察能力:包括阅读理解能力、构建数学模型的能力、程序编码与调试能力、程序的时空性能分析和测试数据的生成能力、各种知识的综合应用能力等;
3. 有区分度:一般3-4题,复赛题目的特点是:1-2题算法和数据结构比较明显、或者和数学关系比较大的题目,得率比较高;1题好上手,但程序量要大一点或数据规模大的题目,考虑全面、得满分也不容易;还有1题一般是搜索、或者算法不明显、或者用到复杂高深一点的数据结构的题目,难度较大。但顺序不一定!!!
Ø 如何备战复赛
1. 做做以往历年的竞赛题和网上的模拟题,熟悉比赛的题型和要求,找出自己的不
足,加强训练;
2. 增强自己编写代码和调试的熟练程度,提高做题的时间和节奏感;
3. 熟练掌握文件的输入/输出操作,新大纲中对复赛的要求;
4. 提高自己设计测试数据的能力;
5. 提高自己做题的正确率(分数/时间);
6. 算法方面:递推、递归、动态规划、贪心、搜索(初中到回溯就差不多了)基本
上是必考!!!对一些经典问题和算法,一定要熟练的不能再熟练;
7. 数据结构方面:字符串经常考,树(尤其二叉树)、图的基本算法(最短路径、最
小生成树等);
Ø 复赛注意事项
1. 认真审题,尤其要注意问题的规模(数据范围),从某种意义上说,问题规模也暗示了你可能的算法。数据小,也许是搜索派上用场的时候;数据大了,可能只能考虑动态规划,数学方法等高算法了。
2. 正确的估计题目的难度和自己的水平。拿到试题后先从总体上分析一下题目,做到心中有数!注意:题目的难易对所有人是公平的,只要最大限度地发挥自己的水平,不要有包袱,考出自己的最佳成绩。
3. 正确地选择题目去做(最擅长、最简单的先完成),合理地安排时间和解题顺序。
4. 复赛中:一定提高正确率!!!解题速度是其次。
5. 复赛考查的算法并不困难,选手在实现上的问题往往要多一些。建议大家:
1) 充分利用草稿纸,不要对自己的“心算能力”太自信!编程熟练的同学喜欢“一气呵成”,拿到题目就开始编码。我认为这样不好,做信息学竞赛题的思维过程是丰富而曲折多变的,考虑问题必须全面,仅凭一时的“感觉”来编程往往是漏洞百出。比如初学者常常忘记做一些初始化工作(远不止变量赋初值这种最简单的),即使有经验的同学也难免因一时疏忽写出几个错误的语句。最要命的是“第一感觉”的算法是错误的或者效率太低(命题者的陷阱),而程序编了大半才发现,时间浪费了不说,还影响了信心和发挥。
2) 做一些复杂的题目,编码采取自顶向下,逐步求精的方法,调试时采用输出中间结果的办法及时找出错误的地方。可以这么说,思路越清晰,对自己程序的算法和编码越了解,调试也会越顺利(一定不要忽视这一点)。
3) 多测试:样例数据、极限(小大)数据、特殊数据,分析能否在规定的时空范围内出解,精度是否够,格式是否对,输入输出文件名、格式是否正确等。
4) 不一定要拿满分,有些题目如果你很拿手,也肯定能做对,那么一定要保证拿满分;但有些题目,在有限的竞赛时间里,你很难拿满分,或者自己觉得没有足够的时间和信心,没有好的方法,那么在很少的时间内用投机取巧的方法(如贪心等)能得到不错的分数,也是一种很大的成功。
Ø 历年复赛题目分布如下(1997年以来)
Ø 复赛试题分析
1.递推(2002年初中组4:过河卒)
[问题描述]
棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上的任一点有一个对方的马(如C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点,如图中的C点和P1,……,P8,卒不能通过对方马的控制点。棋盘用坐标表示,A点(0,0)、B点(n, m) (n,m为不超过20的整数),同样马的位置坐标是需要给出的,C≠A且C≠B。现在要求你计算出卒从A点能够到达B点的路径的条数。
[问题分析]
跳马是一道老得不能再老的题目,我想每位编程初学者都学过,可能是在学回溯或搜索等算法的时候,很多书上也有类似的题目,一些比赛中也出现过这一问题的变形(如NOIP1997初中组的第三题)。有些同学一看到这条题目就去搜索,即使你编程调试全通过了,运行时你也会发现:当n,m=15就会超时。
其实,本题稍加分析就能发现,要到达棋盘上的一个点,只能从左边过来(我们称之为左点)或是从上面过来(我们称之为上点),所以根据加法原理,到达某一点的路径数目,就等于到达其相邻的上点和左点的路径数目之和,因此我们可以使用逐列(或逐行)递推的方法来求出从起点到终点的路径数目。障碍点(马的控制点)也完全适用,只要将到达该点的路径数目设置为0即可。
用F[i,j]表示到达点(i,j)的路径数目,g[i,j]表示点(i, j)有无障碍,g[i,j]=0表示无障碍,g[i,j]=1表示有障碍。
则,递推关系式如下:
F[i,j] = F[i-1,j] + F[i,j-1] {i>0且j>0且g[x, y] = 0}
递推边界有4个:
F[i,j] = 0 { g[x,y] = 1 }
F[i,0] = F[i-1,0] {i > 0且g[x,y] = 0}
F[0,j] = F[0,j-1] {j > 0且g[x,y] = 0}
F[0,0] = 1
考虑到最大情况下:n=20,m=20,路径条数可能会超过MaxLongInt,所以要用高精度(使用Comp类型计数即可)。
程序:见文件夹。
例1、递推举例:储油点
[问题描述]
一辆重型卡车欲穿过1000公里的沙漠,卡车耗油为1升/公里,卡车总载油能力为500公升。显然卡车装一次油是过不了沙漠的。因此司机必须设法在沿途建立几个贮油点,使卡车能顺利穿越沙漠,试问司机如何建立这些贮油点?每一贮油点应存多少汽油,才能使卡车以消耗最少汽油的代价通过沙漠?
编程计算及打印建立的贮油点序号,各贮油点距沙漠边沿出发的距离以及存油量。
No. distance(k.m.) oil(litre)
1 ×× ××
2 ×× ××
3 ×× ××
[问题分析]
设dis[i]─第i个贮油点至终点(i=0)的距离;oil[i]─第i个贮油点的存贮油量。
我们可以用倒推法来解决这个问题。从终点向始点倒推,逐一求出每个贮油点的位置及存油量。下图表示倒推时的返回点。
终点 始点
└───────────┬──┬──────────────┬┘
i=0 i=1 i=2 … …i=n
从贮油点i向贮油点i+1倒推的策略是,卡车在点i和点i+1间往返若干次。卡车每次返回i+1处时正好耗尽500公升汽油,而每次从i+1处出发时又必须装足500公升汽油。两点之间的距离必须满足在耗油最少的条件下使i点贮足i*500公升汽油的要求(0≤i≤n-1)。具体地讲,第一个贮油点i=1应距终点i=0处500km且在该处贮藏500公升汽油,这样才能保证卡车能由i=1处到达终点i=0处,这就是说:dis[1]=500;oil[1]=500。为了在i=1处贮藏500公升汽油,卡车至少从i=2处开两趟满载油的车至i=1处。所以i=2处至少存贮2*500公升汽油,即oil[2]=500*2=1000。另外,再加上从i=1返回至i=2处的一趟空载,合计往返3次。三次往返路程的耗油量按最省要求只能为500公升,即d1,2 = km。
dis[2]=dis[1]+d1,2 =dis[1]+
为了在i=2处贮存1000公升汽油,卡车至少从i=3处开三趟满载油的车至i=2处。所以i=3处至少存贮3*500公升汽油,即oil[3]=500*3=1500。加上i=2至i=3处的二趟返程空车,合计5次。路途耗油量亦应500公升,即d2,3 = km。
dis[3]=dis[2]+d2,3 =dis[2]+
依次类推,为了在i=k处贮藏k*500公升汽油,卡车至少从i=k+1处开k趟满载车至i=k处,即oil[k+1]=(k+1)*500=oil[k]+500,加上从i=k返回i=k+1的k-1趟返程空车,合计2*k-1次。这2*k-1次总耗油量按最省要求为500公升,即dk, k+1= km。
dis[k+1]=dis[k]+dk,k+1
=dis[k]+
最后, i=n至始点的距离为1000-dis[n],oil[n]=500*n。为了在i=n处取得n*500公升汽油,卡车至少从始点开n+1次满载车至i=n,加上从i=n返回始点的n趟返程空车,合计2*n+1次,2*n+1 趟的总耗油量应正好为(1000-dis[n])*(2*n+1),即始点藏油为oil[n]+(1000-dis[n])*(2*n+1)。
由此得出如下的程序框架(程序略):
begin
k:=1;d:=500; { 从i=1处开始向始点倒推}
dis[1]:=500;oil[1]:=500;
repeat
k:=k+1;d:=d+500/(2*k-1);
dis[k]:=d;
oil[k]:=oil[k-1]+500;
until d>=1000;
dis[k]:=1000; {置始点至终点的距离值}
d1:=1000-dis[k-1]; {求贮油点k处至始点的距离}
oil[k]:=d1*(2*k+1)+oil[k-1]; {求始点藏油量}
for i:=0 to k do 输出第i个贮油点的距离为1000-dis[k-i],藏油量为oil[k-i];
end.
程序:见文件夹。
2. 动态规划(2000年初中组3/高中组2: 乘积最大)
[问题描述]
今年是国际数学联盟确定的“2000——世界数学年”,又恰逢我国著名数学家华罗庚先生诞辰90周年。在华罗庚先生的家乡江苏金坛,组织了一场别开生面的数学智力竞赛的活动,你的一个好朋友XZ也有幸得以参加。活动中,主持人给所有参加活动的选手出了这样一道题目:
设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。
同时,为了帮助选手能够正确理解题意,主持人还举了如下的一个例子:
有一个数字串:312, 当N=3,K=1时会有以下两种分法:
3*12=36
31*2=62
这时,符合题目要求的结果是:31*2=62
现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。
[输入]
程序的输入共有两行:
第一行共有2个自然数N,K(6≤N≤40,1≤K≤6)
第二行是一个长度为N的数字串。
[输出]
结果显示在屏幕上,相对于输入,应输出所求得的最大乘积(一个自然数)。
[样例输入]
4 2
1231
[样例输出]
62
[问题分析]
首先,由于问题的规模为6<=N<=40,1<=K<=6,要在长为N的数字串中插入K个乘号使得乘积最大,显然将会超出长整数的范围,所以运算要用高精度乘法。
其次,让我们考虑穷举的方法行不行,由组合数学知识知道:在长为N的数字串中插入K个乘号的方案有C(N-1,K)种,极限数据等于6500000种插入方案,而高精度本身也很费时间,所以穷举算法肯定会超时。看来只有找动态规划这样的高效算法了!!!
那么如何使用动态规划呢?分析发现:问题中只有乘号插入的位置是变化的,取数字串中的任意一段(P1到P2)来考虑,若能求出在其中插入K-1个乘号的最大乘积,则只需穷举第K个乘号的插入位置P(P从初始的P1+K-1开始插入,最多插入到P2-1后)。该乘号把数字串分成了两段,前半段包含K-1个乘号(其最大值已经算出),将它的值与后半段的值相乘得到第K个乘号在位置P时的最大乘积。打擂台选出P在各个位置时得到的最大乘积即为问题的解。
依此类推,把K-1的问题归结为K-2的情况,……,直到求在任意一段中插入1个乘号的最大乘积时,需预先算出在任意一段中插入0个乘号时的最大乘积。而此时的值是已知的(即为该段的数值)。假设C[p1,p2,K]表示在长为N的数字串中,从第p1个数字到第p2个数字之间插入K个乘号的最大乘积,动态转移方程如下:
p2-1
C[p1,p2,k] = MAX (C[p1,p,k-1] * C[p+1,p2,0])
P=p1+k-1
假设输入的数字已保存在d[1..n]中(n<=maxn=40),定义三维数组max存放结果,即:
var max:array[1..maxn,1..maxn,0..maxn-1] of byte;其中max[i,j,0]到max[i,j,maxn-1]存放任意一段的最大乘积。则,动态规划的初始化工作如下:
for i:=1 to n-1 do
for j:=i to n do
begin
for m:=0 to maxn-1 do max[I,j,m]:=0; {清0}
w:=0; {记录高精度数的有效位数}
for m:=j downto I do begin {高精度数的初始化}
max[I,j,w]:=d[m];
w:=w+1;
end;
end;
程序:见文件夹。
例2.动态规划举例(2001年高中3:统计单词个数)
[问题描述]
给出一个长度不超过200的由小写英文字母组成的字母串(约定:该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份(1<k≤40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可以包含this和is ,选用this 之后就不能包含th)。
单词在给出的一个不超过6个单词的字典中。要求输出最大的个数。
[输入格式]
输入数据放在文本文件input3.dat中, 其格式如下:
第一行有二个正整数:(p,k)
p表示字串的行数;
k表示分为k个部分。
接下来的 p行,每行均有20个字符。
再接下来有一个正整数s,表示字典中单词个数。(1≤s≤6)
接下来的s行,每行均有一个单词。
[输出格式]
结果输出至屏幕,每行一个整数,分别对应每组测试数据的相应结果。
[输入输出样例]
输入:1 3
t h i s i s a b o o k y o u a r e a o h
4
is
a
ok
sab
输出:7 [问题分析]
首先,通过分析可以得出下面的结论:当给定的字符串中以同一个字母开头的单词有多个时,只须选用最短的一个,因为该单词受分段的影响最小,所以可以设数组shortest存储给定的字符串中所有位置上的最短单词的长度,shortest[i]=0表示给定的字符串中从第i个字符开始的一段不包含任何单词,否则表示从该位置起包含的最短的单词的长度。
另外,为了方便处理,可以将所有单词按从短到长的次序重新排序,并引入二维数组pos记录单词在给定的字符串中的分布情况,pos[i,j]=true表示在给定的字符串中从第i个字符开始包含第j个单词。
算法上可以从以下几个方面考虑:
1、贪心法
毎次根据数组shortest选定一个分段位置使得被截断的最短单词最少,将这些被截断的最短单词从数组shortest中去除,重复上述过程,直到将给定的字符串分成k段为止;
2、随机化算法
用随机的方法将给定的字符串分成k段,统计毎一段中包含的单词数,全部段包含的单词总数即为问题的一个解,多次随机直到限定时间用完为止,此时输出最优解;
3、结合以上两种方法;
4、动态规划
substr(b,e)表示给定字符串中从第b个字符到第e个字符为止的子串,用数组most记录给定字符串中从第一个字符到任意一个字符为止的子串被分成i段后包含的最多单词数,如most[i,t]表示子串substr(1,t)分成i段后包含的最多单词数,则most[i+1,j]=max(most[i,t]+maxword(substr(t+1,j))),t=i..j-1,其中maxword为求某字符串中最多单词的函数,程序中没有用二维数组,去掉了第一维,递推时下标要从大往小推,类似于杨辉三角用一维数组递推。
程序:见文件夹。
3. 字符串(1998年高中组2: 连接多位数)
[问题描述]
设有n个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343联接成的最大整数为:34331213
又如:n=4时,4个整数7,13,4,246联接成的最大整数为:7424613
程序输入:n
n个数
程序输出:联接成的多位数
[问题分析]
举例说明正常的字符串比较缺陷!如:A=’321’,B=’32’,按照标准的字符串比较规则因为A>B,所以A+B > B+A ,而实际上’32132’< ’32321’。
所以,自定义一种字符串的比较规则:即如果A+B>B+A,则我们认为A>B。且可以证明:如果A+B>=B+A,B+C>=C+B,则一定有:A+C>=C+A。
这样一来,程序就很简单了。分3步,先把n个数字转换成字符串存储;再按照自定义的规则把n个字符串排序;最后按照从大到小的顺序输出这些字符串。
小结:有些问题看起来是数学问题,认真分析会发现用字符串处理很简单。另外,一定要掌握有关字符串的各种函数,以免用到时自己编子程序。
程序:略。
4.贪心(删数问题)
[问题描述]
键盘输入一个高精度的正整数n(小于等于240位),去掉其中任意s个数字后,剩下的数字按原次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小。
[输入格式]
n
s
[输出格式]
顺序列出s个被删去的数字。
[算法分析]
首先,这是一个高精度数。但是否一定用数组存放这个数呢?这要看具体的算法实现。由于正整数n的有效数位为240位,因此我们采用字符串类型存贮n。
为了尽可能逼近目标,我们的“贪心策略”是:每一步总是选择一个使剩下的数最小的数字删去,即按高位到低位的顺序搜索,若各位数字递增,则删最大(最后)的数字;否则删第一个递减区间的首字符,这样便形成了一个新数串。然后回到串首,按上述规则删下一个数字,……,依次类推,直到删除了s个数字为止,输出剩余部分即可。
例如:n=‘1 7 8 5 4 3’ s=4
删数过程如下:
n=‘1 7 8 5 4 3’ 删‘8’
‘1 7 5 4 3’ 删‘7’
‘1 5 4 3’ 删‘5’
‘1 4 3’ 删‘4’
‘1 3’
这样删数问题与“如何寻找递减区间首字符”这样一个简单的问题对应起来。按贪心策略编制的程序框架为:
begin
输入n,s;
while s>0 do
begin
i:=1; {从串首开始找}
while (i<length(n)) and (n[i]<=n[i+1]) do i:=i+1;
delete (n,i,1); {删除字符串n的第i个字符}
s:=s-1;
end;
while (length(n)>1)and (n[1]=‘0’) do delete(n,1,1); {删去串首的无用零}
输出n;
end.
程序:略。
例3、贪心举例:取数游戏
[问题描述]
给出2*n个自然数。游戏双方分别为A方(计算机方)和B方(对奕的人)。只允许从数列两头取数。A先取,然后双方依次轮流取数。取完时,谁取得的数字总和最大为取胜方;双方和相等,属于A胜。试问A方可否有必胜的策略
[输入] 2*n个自然数
[输出]
前2*n行为游戏经过。每两行分别为A方所取的数和B方所取的数,其中B方取数时应给予提示,让游戏者选择取哪一头的数(L/R—左端或右端)。最后一行分别为A方取得的数和和B方取得的数和。
[问题分析]
设自然数列:7 9 3 6 4 2 5 3
我们设计一种方案,从数大的一头让A、B轮流取两个连续数:
A方:7 3 4 5
B方:9 6 2 3
由此得出:
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’);则,程序框架如下:
begin
SA:=0;SB:=0; {计算A方取数和、B方取数和,A方取数的位置标志}
for i:=1 to n do
begin
SA:=SA+a[2*i-1];SB:=SB+a[2*i];
end;
if SA>=SB then j:=1
else begin j:=0;交换SA和SB;end;
lp:=1;rp:=2*n;
for i:=1 to n do {A方和B方依次进行n次对奕}
begin
if ((j=1) and (lp mod 2=1)) or ((j=0)and (lp mod 2=0))
{若A方应取奇位置数且左端位置为奇,
或者A方应取偶位置数且左端位置为偶,
则A方取走左端位置的数}
then begin 输出A方取a[lp];lp:=lp+1;end
else begin 输出A方取a[rp];rp:=rp-1;end;{否则A方取走右端位置的数}
输出提示信息:B方的取数位置(L/R);
repeat
读ch;
if ch=‘L’ then begin 输出B方取a[lp];lp:=lp+1;end;
if ch=‘R’ then begin 输出B方取a[rp];rp:=rp-1;end;
until ch {‘L’,’R’};
end;
输出A方取数的和SA和B方取数的和SB;
end.
程序:略。
5.穷举(2000年高中组1:进制转换)
[问题描述]
我们可以用这样的方式来表示一个十进制数: 将每个阿拉伯数字乘以一个以该数字所处位置的(值减1)为指数,以10为底数的幂之和的形式。例如:123可表示为1*102+2*101+3*100这样的形式。
与之相似的,对二进制数来说,也可表示成每个二进制数码乘以一个以该数字所处位置的(值-1)为指数,以2为底数的幂之和的形式。一般说来,任何一个正整数R或一个负整数-R都可以被选来作为一个数制系统的基数。如果是以R或-R为基数,则需要用到的数码为0,1,....R-1。例如,当R=7时,所需用到的数码是0,1,2,3,4,5和6,这与其是R或-R无关。如果作为基数的数绝对值超过10,则为了表示这些数码,通常使用英文字母来表示那些大于9的数码。例如对16进制数来说,用A表示10,用B表示11,用C表示12,用D表示13,用E表示14,用F表示15。
在负进制数中是用-R作为基数,例如-15(十进制)相当于110001(-2进制),并且它可以被表示为2的幂级数的和数:
110001=1*(-2)5 + 1*(-2)4 + 0*(-2)3 + 0*(-2)2 + 0*(-2)1 + 1*(-2)0
[问题求解]
设计一个程序,读入一个十进制数和一个负进制数的基数, 并将此十进制数转换为此负进制下的数: -R∈{-2,-3,-4,...,-20}
[输入]
输入的每行有两个输入数据。
第一个是十进制数N(-32768<=N<=32767); 第二个是负进制数的基数-R。
[输出]
结果显示在屏幕上,相对于输入,应输出此负进制数及其基数,若此基数超过10,则参照16进制的方式处理。
[样例输入]
30000 -2
-20000 -2
28800 -16
-25000 -16
[样例输出]
30000=11011010101110000(base -2)
-20000=1111011000100000 (base -2)
28000=19180 (base -16)
-25000=7FB8 (base -16)
[问题分析]
其实,本题拿到手的第一个想法是进制转换的常规方法“除R取余,除尽为止,倒取输出”,但当你用一个样例去试的时候,发现毫无规律(如果有的人习惯不好,拿到手自以为是,不喜欢用草稿纸,则会沿着死路往前走)。
其实,分析一下问题的规模N(注意这是第1题),-32767-32768!!!穷举。对任一个-R进制数k(从0开始),计算它对应的十进制数是多少,如果等于N,则输出这个-R进制数,否则继续找下一个-R进制数。
思考:-R进制数好象没有符号位哎:)
程序:见文件夹。
6.简单搜索、回溯(1999年高中组4:邮票面值设计)
[问题描述]
给定一个信封,最多只允许粘贴N张邮票,计算在给定K(N+K≤40)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值MAX,使在1~MAX之间的每一个邮资值都能得到。
例如,N=3,K=2,如果面值分别为1分、4分,则在1分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分);如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,K=2时,7分就是可以得到的连续的邮资最大值,所以MAX=7,面值分别为1分、3分。
样例输入与输出:
INPUT OUTPUT
N=3 K=2 1 3
MAX=7
[问题分析]
首先,看数据规模n+k<=40,显然很大,所以有的选手想用动态规划,但注意本题具有明显的后效性(前面方案的不同使得构成某面值的邮票数不同),所以不满足动态规划的条件。
那么,解决这类问题只能用递归搜索了。但很快有人发现:搜索所能解决的问题规模远远达不到题目的要求,一般搜索到n,k同时超过6,就不行了。而实际的测试数据也就到n=10,k=5,且n+k<13。
所以,我们总结出分区联赛的题目难易程度,其实和数据规模(尤其是实际测试数据)息息相关。有的题目很难,但实际的测试数据很简单(小);而有的题目算法和思想很简单,但数据规模巨大。如果题目难度大且测试数据很刁钻、很大,那么得分率就会极低,这时就相当于去掉这一题比赛(比如2002高中组的矩形覆盖问题)。这种题目这几年基本上是用搜索!所以,选手做题时应充分估计4个题目的难度和命题人的命题动机。对难度小的、有把握的要拿满分;对于难度大,但小数据能很容易算出的要把该拿的分拿住;对于自己能力以外的,不要浪费宝贵的时间(记住:评奖的原则是名次,而不是分数和哪一条题目)。
回到原题中来,首先面值为1的邮票是必须的;此后每加进一种面值都要把当前所能取得的金额记下来。通过k-1次添加得到一种方案。穷举所有的方案得到最优解。
在搜索的过程中,对数据的记录方式很关键。如果仅仅记录一个金额能否被达到,则这样的记录基本上没有用,因为下一次添加邮票时,不知能否从这个金额出发再加上新邮票,推导出达到哪些新面值,必须全部重算,浪费了大量时间;所以,我们记录最少要用几张邮票达到一个面值,这样有利于添加新邮票时迅速判断可行性。
搜索一种新邮票面值的起点(即新邮票的选择策略)应该是:从上一个面值加1开始,直到当前连续取得的最大金额加1。因为过大会造成不连续,过小得不到最优解。
程序:见文件夹。
7.搜索+剪枝优化+构造(1997年高中组1:素数方阵)
[问题描述]
在N*N的棋盘上(1≤N≤10),填入1,2,…,N*N共N*N个数,使得任意两个相邻的数之和为素数。
当N=4时,一种可以填写的方案如下:
在这里我们约定:左上角的格子里必须填数字1。
程序要求:
输入:N;
输出:如有多种解,则输出第一行、第一列之和为最小的排列方案;若无解,则输出“NO!”。
[问题分析]
由于素数的变换是没有规律的,本题只能搜。因为N的值是从1到10的之间的任意一个自然数,每个方格中的数字不相同,所以采用递归回溯的方法实现。具体讲,就是将所有方格编一个次序,根据“如有多种解,则输出第一行、第一列之和为最小的排列方案“这句话,填数的顺序应是先填第一行第一列(1),以后选择填入的数应该从小到大依次选择,首先选一个2到N*N之间的自然数填入第一行第二列,要求填入的数与已填的相邻方格中的数之和为素数;然后再用相同的方法填第一行的第三列到第N列,填完第一行后,再依次填第一列中的第二行到第N行。依此类推,再填第二行的第二列到第N列,再填第二列的第三行到第N行,……,直到右下角最后一个数填好为止(再按要求输出)。
在填数的过程中有几个要求:一要保证填入的数没用过(很简单,设一个used[1..n*n],值为布尔型);二是当前格子选不到数填时要回溯,……,直到所有情况都搜索到了(回溯的控制);三是判断填入的数是否合法(类似于N皇后问题的判断合法性)。
对于第三个问题,如果每填入一个数,都要判断它与上方和左方的格子中的数之和是否为素数,效率会很低。所以想到了用构造(查表)法判断素数。因为任意一个格子的最大数为N*N,所以相邻两个格子的数之和最大为2N*N。即把1到2N*N中的素数求出来保存好(200以内的素数太简单了),然后每次判断只要查表了。
小结:1.素数总是一奇一偶的和,所以,行列上肯定是奇数偶数相隔的,优化?
2.如果去掉a[1,1]=1这个约定,则发现:只有当a[1,1]中填奇数时问题才有可能有解,为什么?因为1..n*n个数中奇数的个数一定大于等于偶数的个数,再结合1。
3.求所有解及总数。
程序:见文件夹。
发表于 dayu
( 2007年11月13日, 08:31:10 AM CST )
Permalink
评论 [0]
10. 一位艺术史学家有20000 幅1024 * 768 的真彩色图像,如果将这些图像以位图形式保存
17.按照二叉数的定义,具有3个结点的二叉树有( )种。
14. 设栈S的初始状态为空,元素a, b, c, d, e, f, g依次入栈,以下出栈序列不可能出现的有 1、 计算ex,lnx。X=1,2,3…..10 2、 计算ex,lnx。X=1.1,1.2,1.3…..2.0 3、 计算10个数的和、平均值、积 4、 输入n,计算n的阶乘(n!) 5、 计算1!,2!….20! 6、 求两个数的最大公约数和最小公倍数 7、 输入字母a、b….z,反序输出 8、 求菲波拉切数 const m=40; {function fla(var r:yu;f:integer):boolean; readln(v[i],w[i],q[i]); i:=1; for l:=0 to t do
if (l>v1[i][k]) and ((f[i-1][l-v1[i][k]]+p[i][k])>f[i-1][l]) then begin f[i][l]:=(f[i-1][l-v1[i][k]]+p[i][k]); end (budget.pas/c/cpp) 【问题描述】 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有0个、1个或2个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的N元。于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是10元的整数倍)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。 设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为: v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号) 请你帮助金明设计一个满足要求的购物单。 【输入文件】 输入文件budget.in 的第1行,为两个正整数,用一个空格隔开: N m (其中N(<32000)表示总钱数,m(<60)为希望购买物品的个数。) 从第2行到第m+1行,第j行给出了编号为j-1的物品的基本数据,每行有3个非负整数 v p q (其中v表示该物品的价格(v<10000),p表示该物品的重要度(1~5),q表示该物品是主件还是附件。如果q=0,表示该物品为主件,如果q>0,表示该物品为附件,q是所属主件的编号) 【输出文件】 输出文件budget.out只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。 【输入样例】 1000 5 800 2 0 400 5 1 300 5 1 400 3 0 500 2 0 【输出样例】 2200 发表于 dayu ( 2007年10月24日, 11:11:22 AM CST ) Permalink 评论 [0]算法实例-关于旅行者的背包问题 基本问题: 0/1背包问题中,需要对容量为C的背包进行装载。从N个物品中选取装入背包的物品,每件物品I的重复为W(I),价值为P(I)。如何装入价值最大的物品? 算法: 贪心准则1-从剩余的物品中找出一个可以装入背包的价值最大的物品。 贪心准则2-从剩余的物品中找出可以装入背包的重量最小的物品。 贪心准则3-计算价值密度P(I)/W(I)。从剩余的物品中选择可装入包的P/W(I) 物品。 贪心算法之K阶优化-从答案中取出K件物品,并放入另外K件,获得的结果不会比原来的差。这样优化整个放入物品的序列。 动态规划-用递归进行计算求解。每一步求出基于现在状况下的最优解。(特点-计算复杂) 回溯算法-首先形成一个递归算法,去找到可获得的最大收益。即列出所有的候选解,然后依次检查每一个,在检查完所有或部分候选解后,即可找出所需要的解。(特点-候选解数量庞大) 变形问题: ·货郎的背包 ·野外生存者的背包 ·公司经理(?)的背包。 发表于 dayu ( 2007年10月24日, 11:08:09 AM CST ) Permalink 评论 [0] |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
©下落的石头 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||