JSOI09第一轮省队选拔试题解题报告.docx

上传人:b****3 文档编号:3834183 上传时间:2022-11-25 格式:DOCX 页数:18 大小:94.08KB
下载 相关 举报
JSOI09第一轮省队选拔试题解题报告.docx_第1页
第1页 / 共18页
JSOI09第一轮省队选拔试题解题报告.docx_第2页
第2页 / 共18页
JSOI09第一轮省队选拔试题解题报告.docx_第3页
第3页 / 共18页
JSOI09第一轮省队选拔试题解题报告.docx_第4页
第4页 / 共18页
JSOI09第一轮省队选拔试题解题报告.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

JSOI09第一轮省队选拔试题解题报告.docx

《JSOI09第一轮省队选拔试题解题报告.docx》由会员分享,可在线阅读,更多相关《JSOI09第一轮省队选拔试题解题报告.docx(18页珍藏版)》请在冰豆网上搜索。

JSOI09第一轮省队选拔试题解题报告.docx

JSOI09第一轮省队选拔试题解题报告

JSOI09第一轮省队选拔试题解题报告

Bysx349

一、二叉树问题

【问题描述】

如下图所示的一棵二叉树的深度、宽度集结点间距离分别为:

深度:

4宽度:

4(同一层最多结点个数)

结点间距离:

 

为8(3×2+2=8)

 

为3(1×2+1=3)

注:

结点间距离的定义:

由结点向根方向(上行方向)时的边数×2,

与由根向叶结点方向(下行方向时的边数之和)。

【输入】

输入文件第一行为一个整数(1≤n≤100),表示二叉树结点个数。

接下来的n-1行,表示从结点x到结点y(约定根结点为1),最后一行两个整数u、v,表示求从结点u到结点v的距离。

【输出】

三个数,每个数占一行,依次表示给定二叉树的深度、宽度及结点u到结点v间距离。

【样例】

输入:

10

12

13

24

25

36

37

58

59

610

86

输出:

4

4

8

【算法分析】

这道题主要考察了对于二叉树的了解,其次对于题意的正确理解在这道题中也起到了非常大的作用。

首先我们来看这一句话:

“接下来的n-1行,表示从结点x到结点y(约定根结点为1)”。

分析这句话后可知,在其后给出的x,y并不是有序的(不一定x就是y的父结点)。

忽略这一点将会使程序出现致命性的错误。

了解这一点后,程序就很简单了。

我们用领接矩阵来记录所有相连的结点(标记所有的connect[x,y]以及connect[y,x]),由于已知结点1必然为根结点,所以从结点1开始进行一次DFS即可建树。

在进行DFS前,我们再回头看一下题目,我们要求的是:

“给定二叉树的深度、宽度及结点u到结点v间距离。

”深度我们用Deep[I]来记录,宽度就是Deep[I]中出现最多的那个数的出现次数(比如Deep数组中有3个4、4个3、2个2、1个1,那么宽度就是出现最多的数(3)的出现次数(4)),而结点间距离就是(Deep[起始结点]-1)*2+Deep[结束结点]-1(因为我们要求的是某一点到根结点的线段数,所以比其深度小1)。

这样分析以后,我们只需在DFS的过程中将每棵树的深度记录下来,然后不断判断MAX值即可。

【程序清单】

ProgramTree;

Var

Con:

Array[1..100,1..100]ofBoolean;

DeepSum,Deep:

Array[1..100]ofLongint;

A,B,N,I,MaxDeep,MaxBroad:

Longint;

ProcedureSearch(Now:

Longint);

Var

I:

Longint;

Begin

ForI:

=1toNdo

IfCon[Now,I]and(Deep[I]=0)

Then

Begin

Deep[I]:

=Deep[Now]+1;

Search(I);

End;

End;

Begin

Assign(Input,’Tree.in’);

Assign(Output,’Tree.out’);

Reset(Input);

Rewrite(Output);

Read(N);

ForI:

=1toN-1do

Begin

Read(A,B);

Con[A,B]:

=True;

Con[B,A]:

=True;

End;

Read(A,B);

Deep[1]:

=1;

Search

(1);

ForI:

=1toNdo

Begin

Inc(DeepSum[Deep[I]]);

IfDeepSum[Deep[I]]>MaxBroad

Then

MaxBroad:

=DeepSum[Deep[I]];

IfDeep[I]>MaxDeep

Then

MaxDeep:

=Deep[I];

End;

Writeln(MaxDeep);

Writeln(MaxBroad);

Writeln((Deep[A]-1)*2+Deep[B]-1);

Close(Input);

Close(Output);

End.

二、照明灯

【问题描述】

在一条直线上,有n个三角形(假设其为n座山峰)。

对于每座山峰均有四个参数(x,y,d,h),其中:

x表示山峰前点,y表示山峰后点,d表示山峰位置,h表示山峰高度。

这四个参数均为整数(1

同时,约定山峰的底角均为小于90°的角。

给出一个位置P,在P处有一个灯杆,灯杆上部装有一个灯泡。

【编程任务】

灯最少离地面多高时,才能照到全部不被山峰覆盖的地面。

上图中的OA,BC,FX即使不被山峰覆盖的地面。

【输入】

输入文件的第一行有三个整数n,X,P(1≤n≤100),接下来的n行,每行有四个参数,用以表示一座山峰(数据间用一个空格分隔)。

【输出】

一个实数(保留小数二位)。

表示满足要求时,灯离地的最小高度。

【样例】

输入:

2105

1321

6971

输出:

4.00

【算法分析】

首先考虑下这个问题:

什么地方的地面最难被照到?

显然,是紧贴着山脚的那个点。

所以,我们只要考虑每个空当(即“不被山峰覆盖的地面”)的左右两个端点能否被找到就可以了。

怎么确定这个端点呢?

由于山峰的位置已经告诉你了,我们用一个布尔型数组State[I]来记录以I为左端点,长度为1个单位的线段是否被山峰覆盖。

然后扫一遍整个数组,将所有的需要计算的点挑出来计算。

接下来我们看根据这个点怎么计算它所需要的灯泡高度。

可能我们会先产生这样的想法:

我们现在把所有的组成山的两条线都延长,这些延长线必然在直线x=P这条线上有交点。

我们只要找出这些交点中y轴坐标最大的那个点,它就是Q点。

显然,这个算法是有瑕疵的,比如下面这张图:

显然,在这种情况下,灯的位置并不在山峰的延长线上。

上述思想的理论基点在于这样一个想法:

每一个被照亮的山脚最低需要的灯泡高度为将这个山脚所贴的山峰那条边延长后与直线x=p的交点。

这个想法有一个错误的地方在于,当山峰的这条边延长时,如果与另一座山峰相交,光线就会被另一座山峰挡住。

所以我们将这个想法进一步改进一下:

假设这个点在P的左侧(右侧也可依样处理),那么我们将这个点和P点左侧的所有山峰(这里可以小小的优化为这个点与P点间所有的山峰)顶端连一条线,然后延长到P,选择其中的最高点。

这样可以保证从这个点射到某个山脚的光线不会被阻挡住。

如何求延长线与直线x=P的交点呢?

我们应用相似三角形的原理,假设当前点横坐标为now,取第I个山峰,则有|now-p|:

|now-d[i]|=height:

h[i](height为所需山峰高度),因此有height=(|now-p|*h[i])/|now-d[i]|。

最后选择所有height中的最大值即为灯泡的最小值。

【程序清单】

ProgramLamp;

Var

x,y,N,L,P,I,J,K:

Longint;

d,h:

Array[1..100]ofLongint;

State:

Array[-1..1000]ofBoolean;

Min:

Extended;

ProcedureQ_Sort(Left,Right:

Longint);

Var

I,J,Mid:

Longint;

Begin

I:

=Left;

J:

=Right;

Mid:

=d[(I+J)Div2];

Repeat

Whiled[I]

Whiled[J]>MiddoDec(J);

IfI<=J

Then

Begin

d[0]:

=d[I];d[I]:

=d[J];d[J]:

=d[0];

h[0]:

=h[I];h[I]:

=h[J];h[J]:

=h[0];

Inc(I);Dec(J);

End;

UntilI>J;

IfI

IfJ>LeftThenQ_Sort(Left,J);

End;

ProcedureSearch(Now:

Longint);

Var

I:

Longint;

Height:

Extended;

Begin

IfNow

Then

ForI:

=1toK-1do

Begin

Height:

=h[I]/(d[I]-now)*(p-now);

IfHeight>Min

Then

Min:

=Height;

End

Else

ForI:

=KtoNdo

Begin

Height:

=h[I]/(now-d[I])*(p-now);

IfHeight>Min

Then

Min:

=Height;

End;

End;

Begin

Assign(Input,’Lamp.in’);

Assign(Output,’Lamp.out’);

Reset(Input);

Rewrite(Output);

Read(N,L,P);

ForI:

=1toNdo

Begin

Read(x,y,d[I],h[I]);

ForJ:

=xtoy-1do

State[J]:

=True;

End;

Q_Sort(1,N);

I:

=1;

Whiled[I]<=pdo

Inc(I);

K:

=I;

State[-1]:

=True;

State[X]:

=True;

Min:

=0;

ForI:

=0toX-1do

IfnotState[I]

Then

Begin

IfState[I-1]

Then

Search(I);

IfState[I+1]

Then

Search(I+1);

End;

Writeln(Min:

0:

2);

Close(Input);

Close(Output);

End.

三、数列计数

【问题描述】

由1,2,3,……,P(P≤9),这P个数字组成长度为L的数列(1≤L≤30)。

【编程任务】

要求不能有K个或大于K个1相连的数列个数(1

【输入】

仅一行,包含有三个整数(用一个空格分开),分别表示P、L、K。

【输出】

一个整数。

表示合理的数列个数。

【样例】

输入:

332

输出:

22

【样例说明】

121212312

122213313

123221321

131222322

132223323

133231331

232332

233333

(合理的数列个数共22种)。

【算法分析】

显然这是一道数学题,我们就应该考虑用数学方法来解决。

我们很容易得到这样的一个递推式:

为长度为n,且末尾有t个1的数列总数。

那么有递推式:

最后求

初始边界为

显然,这个递推式的复杂度为O(L*K*K)即O(L3)左右,因为L仅为30,因此即使再加上高精度运算,这个复杂度还是可以忍受的。

但是在二维数组中使用高精度有些繁琐,所以我们还可以进一步推导,将

(1)式代入

(2)式可以得到:

我们可以将这个等式转化为一维的式子,即:

其中

表示长度为i,末尾不以1结尾的数列个数。

最后求的值相应变为

初始边界变为

空间复杂度降为O(L),时间复杂度也降为O(L*K)。

为了获得更快的速度,进一步推导该式子,有:

该式子的适用范围为n≥2,所以边界条件增加1个为

(因为

无法由该式推出)由于K不等于1,所以这个边界是可以的。

由于这道题最后的数据极大(约为4*1019),所以必然考虑要用高精度计算,为了简化运算,我们可以发现,当n≤k时,后面的

始终为0,也即有:

这样,可以省去数组f中的负数部分。

这样一来,时间复杂度进一步降低为O(L),数据量再大一些也可以承受。

显然,根据该递推式可以考虑O

(1)的算法,但那需要更大量的数学推导,限于篇幅,在这里就不进一步赘述了。

有兴趣的同学可以进一步自行研究。

【程序清单】

ProgramCount;

Type

Num=Array[0..100]ofInteger;

Var

P,L,K,I:

Longint;

F:

Array[0..31]ofNum;

Ans:

Num;

ProcedureCheng(Str:

Num;N:

Longint;VarStr1:

Num);

Var

I:

Longint;

Begin

ForI:

=1toStr[0]do

Str1[I]:

=Str[I]*N;

ForI:

=1toStr[0]do

Begin

Str1[I+1]:

=Str1[I+1]+Str1[I]Div10;

Str1[I]:

=Str1[I]Mod10;

End;

IfStr1[Str[0]+1]<>0

ThenStr1[0]:

=Str[0]+1

ElseStr1[0]:

=Str[0];

End;

ProcedureAdd(Str:

Num);

Var

I,T:

Longint;

Begin

IfStr[0]>Ans[0]

ThenT:

=Str[0]

ElseT:

=Ans[0];

ForI:

=1toTdo

Ans[I]:

=Ans[I]+Str[I];

ForI:

=1toTdo

Begin

Ans[I+1]:

=Ans[I+1]+Ans[I]Div10;

Ans[I]:

=Ans[I]Mod10;

End;

Ans[0]:

=T;

IfAns[T+1]<>0ThenAns[0]:

=T;

End;

ProcedureMinus(Str,Str1:

Num;VarStr2:

Num);

Var

I:

Longint;

Begin

ForI:

=1toStr[0]do

Str2[I]:

=Str[I]-Str1[I];

ForI:

=1toStr[0]do

WhileStr2[I]<0do

Begin

Str2[I+1]:

=Str2[I+1]-1;

Str2[I]:

=Str2[I]+10;

End;

WhileStr2[Str2[0]]=0do

Dec(Str2[0]);

End;

Begin

Assign(Input,’Count.in’);

Assign(Output,’Count.out’);

Reset(Input);

Rewrite(Output);

Read(P,L,K);

F[0,1]:

=1;

F[0,0]:

=1;

F[1,1]:

=P-1;

F[1,0]:

=1;

ForI:

=2toLdo

Begin

Cheng(F[I-1],P,F[I]);

IfI>K

Then

Begin

Cheng(F[I-K-1],P-1,F[31]);

Minus(F[I],F[31],F[I]);

End;

End;

Fillchar(Ans,Sizeof(Ans),0);

ForI:

=L-K+1toLdo

Add(F[I]);

ForI:

=Ans[0]downto1do

Write(Ans[I]);

Close(Input);

Close(Output);

End.

四、谜题

【问题描述】

有些人花很多时间去解各种各样的谜题,这里要考虑的一种典型的谜题看上去是这个样子的:

这里给出了一个包含各种符号的等式(包括横向等式与纵向等式),必须找到将符号一一对应地替代为数字0,…,9的方法。

如果你曾经尝试过解这种谜题,你就会发现这是一件多么烦琐的事情。

【编程任务】

给你一组包含符号A,B,…,J的等式。

请你找到将符号替代位数字的方法,使得所有的等式同时成立。

所有有数字组成的数都是正的,并且第一个数字都不是0。

如果有多个解法存在,则只打印出将替换后的数字串看成一个整数是数值最小的一组解,如:

解2345678901和解1987654320都满足条件,则只打印出1987654320,因为它在“数值”上最小。

【输入】

输入由一行单独的整数n开始(n代表等式的个数,1≤n≤10)。

接下来是n个等式。

每个等式的语法如下:

equation=argument1[‘+’|‘-’|‘*’|‘/’]argument2‘-’result

argument1=digit{digit}*

argument2=digit{digit}*

result=digit{digit}*

digit=[‘A’|…|‘J’]

注意:

并不是所有A到J的符号都会出现在等式中。

然而,如果其中一个符号出现了,那么所有在字母排序上比它小的符号就同样会出现。

所有出现的数最多有11个数字(即最多是11为整数)。

【输出】

输出仅一行,格式如下:

按字母表顺序排列的所有出现字符

一个箭头‘-->’

对出现的符号在树枝上最小的替换。

如果没有解,则输出一行‘Nosolution’

【样例1】

输入

6

EBCE/CED=AE

FBGB-FHBG=ADC

DIAA-GIHJ=FJHF

EBCE-FBGB=DIAA

CED+FHBG=GIHJ

AE*ADC=FJHF

输出

ABCDEFGHIJ-->1746823509

【样例2】

输入

1

A+A=AA

输出

A-->Nosolution

【算法分析】

这道题很眼熟,和“虫食算”很相似,而且比“虫食算”还稍简单一点。

显然这道题的做法应该是按字母顺序DFS搜出第一个解即输出。

(因为题目要求字典序,所以按字母顺序搜索,显然第一个解组后得到的数字序列比后面的小)

搜索时使用函数search(x,S)来进行,其中x表示当前搜索第x个字母,S表示当前还未检查过的式子的集合。

函数中要进行:

判断剩下式子中再搜出前x-1个字母后是否成立,以及给第x个字母赋值。

S的作用在于,当我进行下一步搜索时,不需要将n个式子全部搜索一遍,而只需搜索剩余没检查过的式子即可。

鉴于集合的运算相对比较不变,我们考虑用一种类似Hash的方法:

我们用数字来表示这个集合S,将该数字表示为二进制后,它的倒数第i位表示第i各式子的状态,这样每种状态对应一个数字,显然,由于式子总数为11,所以有S<1024。

因为将S作为数字,所以相应的检查运算就要利用数字之间的运算。

我们利用PASCAL中的位运算来进行:

加入第i个式子:

S:

=S+2i

减去第i个式子:

S:

=S-2I

判断第i个式子是否在S中:

Sand1shl(i-1)<>0

接下来考虑剪枝,显然我们可以看见一个基本的剪枝,当某个式子AopB=C的所有字母值都已经搜索出来之后,我们可以判断这个式子的正确性,如果正确,则在S中删除该式子(已判定必正确)否则回溯(显然,这个式子中有些地方不正确)进而达到剪枝的目的。

这个剪枝是一个很基本的剪枝,很容易想到,而且比较容易实现(通过PASCAL中字符串函数的应用)。

但是显然这个剪枝的力度不强,比如说当每个式子都有10个未知数时,显然要将所有字母都搜索出来才能利用它来剪枝,此时的时间复杂度仍然是没有剪枝时的O(n!

)左右。

我们可以将这个剪枝拓展一下。

比如式子AopB=C中,如果A和B的值都已经搜索出来了,而C还没有全部搜索出来(如果全部搜索出来就可利用上面那个较弱的剪枝)那么显然我们可以计算AopB的值与C进行比较。

如果与已搜索出的值不相同,那么就可以直接回溯到上层,如果相同,则可以直接确定C中剩下字母的值(显然为了式子正确,C必然为AopB的值),并将该式子从S中去掉。

这个剪枝的力度比上一个强很多,同时也可以减少搜索的次数(因为已经确定了后面的值)。

但我们还可以将这个剪枝进一步拓展:

当A,B,C中有两个计算出来后,可以将另一个计算出来,判断正确性。

正确,则可以确定一部分未确定的值,不正确,则立刻回溯。

这一步扩展后可以使搜索速度加快更多。

另一个重要的剪枝是通过不等式剪枝。

如搜索中有式子3G*2DE=3F2C,显然,这个式子后面无论搜索出什么值都不会成立,这是我们一眼就看得出来的,那如何让电脑来判断呢?

我们先把左边所有的未知数都换成9,那么左边可以达到的最大值就是39*299=11661,再把所有未知数换成1,那么左边可以达到的最小值就是31*211=6541。

再看右边,将所有未知数换成9,得最大值3929;再把所有未知数换成1,得最小值3121。

显然,如果左边的最大值(这里是11661)小于右边的最小值(3121)(这里显然是大于的),或者左边的最小值(6541)大于右边的最大值(3929)(显然这一点成立),那么也可以立刻回溯到上一层。

这个剪枝可以运用在加、减、乘三种运算上,但是除要运用到实数,所以比较麻烦。

如何克服这一点呢?

因为减法是加法的逆运算,除法是乘法的逆运算,所以我们可以在一开始就将减法和除法与处理为加法和乘法,这样在处理时,只需要处理乘法和加法的情况就可以了。

【程序清单】

(鉴于篇幅过长,在此略去)

小结

在这次JSOI09省对第一轮选拔的试题中,只有最后一题需要复杂的思考过程,另三道题目相对来说难度不大。

但是在选拔赛过程中,需要时刻对于题目和自己的程序有着清醒的认识。

譬如我在比赛中,对于第一题中的输入理解有误,没有考虑到给出的两个结点并不一定是按父子结点排好的,所以直接考虑使用并查集来做。

显然,这一算法必然得不到满分。

而第二题中,对于灯泡要照亮的部分要判断清楚,尤其是这道题中给出的山峰是以点为标记的,而要照亮的地方则是线段。

第三题的递推公式则只要能够发现第一个公式就可以完成这个题目了,但是如果能够再花一点时间往后推导,那么效率肯定更高。

最后一题中的两个大剪枝(依据等式性质和不等式性质)相对来说比较难想到,在比赛中应该有选择性地选择变成容易较简单的剪枝,比如前一个剪枝的基础样式比较容易想到,在这个基础上,如果时间充分那么完全可以考虑进一步深入,否则的话也应该果断放弃(因为深入后的编程复杂度由于修改处的增多而大大增加)。

这也是在比赛中应该做到的。

总的来说,这一次比赛对我来说是一次很大的磨炼。

在这次比赛中,充分暴露出我在正式比赛中对于细节相当不注意的缺点,这一点对于比赛是极为致命的。

希望在以后的训练中,我能够在这一点上有所改善。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 能源化工

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1