第九届全国青少年信息学奥林匹克联赛复赛.docx
《第九届全国青少年信息学奥林匹克联赛复赛.docx》由会员分享,可在线阅读,更多相关《第九届全国青少年信息学奥林匹克联赛复赛.docx(20页珍藏版)》请在冰豆网上搜索。
第九届全国青少年信息学奥林匹克联赛复赛
第九届全国青少年信息学奥林匹克联赛(N0IP2003)
复赛提高组解题报告
题一源程序题二源程序题三源程序题四源程序
题一神经网络
【问题背景】
人工神经网络(ArtificialNeuralNetwork)是一种新兴的具有自我学习能力的计算系统,在模式识别、函数逼近及贷款风险评估等诸多领域有广泛的应用。
对神经网络的研究一直是当今的热门方向,兰兰同学在自学了一本神经网络的入门书籍后,提出了一个简化模型,他希望你能帮助他用程序检验这个神经网络模型的实用性。
【问题描述】
在兰兰的模型中,神经网络就是一张有向图,图中的节点称为神经元,而且两个神经
元之间至多有一条边相连,下图是一个神经元的例子:
神经元〔编号为1)
图中,X1—X3是信息输入渠道,Y1-Y2是信息输出渠道,C1表示神经元目前的状态,
Ui是阈值,可视为神经元的一个内在参数。
神经元按一定的顺序排列,构成整个神经网络。
在兰兰的模型之中,神经网络中的神
经无分为几层;称为输入层、输出层,和若干个中间层。
每层神经元只向下一层的神经元
输出信息,只从上一层神经元接受信息。
下图是一个简单的三层神经网络的例子。
兰兰规定,Ci服从公式:
(其中n是网络中所有神经元的数目)
公式中的Wji(可能为负值)表示连接j号神经元和i号神经元的边的权值。
当Ci大于0时,该神经元处于兴奋状态,否则就处于平静状态。
当神经元处于兴奋状态时,下一秒它会向其他神经元传送信号,信号的强度为Ci。
如此.在输入层神经元被激发之后,整个网络系统就在信息传输的推动下进行运作。
现在,给定一个神经网络,及当前输入层神经元的状态(Ci),要求你的程序运算出最后网络输出层的状态。
【输入格式】
输入文件第一行是两个整数n(1≤n≤200)和p。
接下来n行,每行两个整数,第i+1行是神经元i最初状态和其阈值(Ui),非输入层的神经元开始时状态必然为0。
再下面P行,每行由两个整数i,j及一个整数Wij,表示连接神经元i、j的边权值为Wij。
【输出格式】
输出文件包含若干行,每行有两个整数,分别对应一个神经元的编号,及其最后的状态,两个整数间以空格分隔。
仅输出最后状态非零的输出层神经元状态,并且按照编号由小到大顺序输出!
若输出层的神经元最后状态均为0,则输出NULL。
【输入样例】
56
10
10
01
01
01
131
141
151
231
241
251
【输出样例】
31
41
51
[分析]本题是比较简单的,但要注意神经元的层数,只输出最大层(输出层)的状态非零的神经元的状态,在中间层中只有神经元处于兴奋状态时(Ci>0)才会向下一层传送信号。
[PASCAL源程序]
programNOIP2003_1_Network;
const
maxn=200;maxp=200;
var
i,j,n,p,maxlayer:
integer;
w:
array[0..maxp]oflongint;{存放边的权值}
start,terminal:
array[0..maxp]ofbyte;{存放边的起点与终点}
u,c:
array[0..maxn]oflongint;{存放神经元的阀值与状态值}
layer:
array[0..maxn]ofbyte;{存放神经元的层数}
f1,f2:
text;fn1,fn2,fileNo:
string;
flag:
boolean;
begin
write('InputfileNo:
');
readln(fileNo);
fn1:
='network.in'+fileNo;
fn2:
='network.ou'+fileNo;
assign(f1,fn1);reset(f1);
assign(f2,fn2);rewrite(f2);
readln(f1,n,p);
fori:
=1tondoreadln(f1,c[i],u[i]);
fillchar(layer,sizeof(layer),0);
fori:
=1topdobegin
readln(f1,start[i],terminal[i],w[i]);{读入边的起点,终点,权}
layer[terminal[i]]:
=layer[start[i]]+1;{计算终点的层数(比起点大1)}
end;
close(f1);
maxlayer:
=layer[terminal[p]];{求最大层数,即输出层的层数}
fori:
=1tondo{计算非输入层的节点i的状态值}
iflayer[i]>0thenbegin
forj:
=1topdo
if(terminal[j]=i)and(c[start[j]]>0){与目标节点i有边相连的节点j且其状态处于兴奋时(Cj>0)才向节点I传送信号}
thenc[i]:
=c[i]+w[j]*c[start[j]];
c[i]:
=c[i]-u[i];
end;
{输出结果}
flag:
=true;
fori:
=1tondo
if(layer[i]=maxlayer)and(c[i]>0)thenbegin
writeln(f2,i,'',c[i]);
flag:
=false;
end;
ifflagthenwriteln(f2,'NULL');
close(f2);
end.
[点评]基本题。
题目阅读起来有些费神,题中边的权值W,神经元的状态值C,阀值U,均未明确指出其取值范围,反映出命题不严谨。
题二侦探推理
【问题描述】
明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中,于是他召集了一群同学玩推理游戏。
游戏的内容是这样的,明明的同学们先商量好由其中的一个人充当罪犯(在明明不知情的情况下),明明的任务就是找出这个罪犯。
接着,明明逐个询问每一个同学,被询问者可能会说:
证词中出现的其他话,都不列入逻辑推理的内容。
明明所知道的是,他的同学中有N个人始终说假话,其余的人始终说真。
现在,明明需要你帮助他从他同学的话中推断出谁是真正的凶手,请记住,凶手只有一个!
【输入格式】
输入由若干行组成,第一行有二个整数,M(1≤M≤20)、N(1≤N≤M)和P(1≤P≤100);M是参加游戏的明明的同学数,N是其中始终说谎的人数,P是证言的总数。
接下来M行,每行是明明的一个同学的名字(英文字母组成,没有主格,全部大写)。
往后有P行,每行开始是某个同学的名宇,紧跟着一个冒号和一个空格,后面是一句证词,符合前表中所列格式。
证词每行不会超过250个字符。
输入中不会出现连续的两个空格,而且每行开头和结尾也没有空格。
【输出格式】
如果你的程序能确定谁是罪犯,则输出他的名字;如果程序判断出不止一个人可能是罪犯,则输出CannotDetermine;如果程序判断出没有人可能成为罪犯,则输出Impossible。
【输入样例】
315
MIKE
CHARLES
KATE
MIKE:
Iamguilty.
MIKE:
TodayisSunday.
CHARLES:
MIKEisguilty.
KATE:
Iamguilty.
KATE:
Howareyou?
?
【输出样例】
MIKE
[分析]基本思路有两种:
一是可以穷举M个人中有N个人始终说假话的所有组合,据此出发,判断每句证词的真伪,再推断谁是罪犯,但这样做运算量大;二是可以穷举M个人中的任意一个是罪犯,由此再来判断每句证词的真伪,推断谁说真话谁说假话,这样做运算量小得多。
这里采用后者。
有几点必须注意:
一,不能说找到某人是罪犯或可能是罪犯就完事了,还必须确保是否还有别人是罪犯或可能是罪犯;二,如何处理关于星期的证词呢?
还是可以采用穷举;三,某人的证词可能前后矛盾,在给某人标记他是始终说真话还是始终说假话时,一定要考察他此前的诚信记录;因此,对某人的诚信记录要有4种不同的区分,可用0表示此人尚无有效记录(未说过真话也未说过假话),用1表示此人说真话,用2表示此人说假话,用3表示此人说的话与他前面的证词冲突;四,如何判断最初穷举时设定的前提(某人是罪犯)是否是本题的一个解呢?
如果有人的诚信记录为3,则肯定不是本题的解;但是也不能强求诚信记录为2的人的总数一定要等于n,而是只要诚信记录为2的人不超过n且诚信记录为1的人不超过m-n即可,因为诚信记录为0的人可能说真话也可能说假话,他们只是没有说话,或只说了废话。
五,由于证词要反复用于判断,可以先剔除其中的无效证词;为处理方便,将有效证词分为两部分:
不含星期的和含星期的。
[PASCAL源程序]
programNOIP2003_2_Logic;
const
maxm=20;
dow:
array[1..7]ofstring=('Sunday.','Monday.','Tuesday.','Wednesday.',
'Thursday.','Friday.','Saturday.');
var
i,j,k,weekday,m,n,p,p1,p2,p3,index,resolution,total1,total2:
byte;
name:
array[1..maxm]ofstring;{存放人名}
witness10,witness20:
array[1..100]ofbyte;{存放说话人的序号,分为两类,前者存放不含星期的证词的说话人的序号,后者存放只含星期的证词的说话人的序号}
witness1,witness2:
array[1..100]ofstring;{存放证词,分为两类,第一类是不含星期的证词,第二类是只含星期的证词}
name0,temp,temp0,temp1,temp2:
string;
truth,truth0:
array[1..maxm]ofbyte;{存放诚信记录}
f1,f2:
text;fn1,fn2,fileNo:
string;
flag:
boolean;
begin
write('InputfileNo:
');readln(fileNo);
fn1:
='logic.in'+fileNo;fn2:
='logic.ou'+fileNo;
assign(f1,fn1);reset(f1);assign(f2,fn2);rewrite(f2);
readln(f1,m,n,p);
fori:
=1tomdoreadln(f1,name[i]);
total1:
=0;total2:
=0;
fori:
=1topdobegin{对证词预处理}
readln(f1,temp);
index:
=pos(':
',temp);
temp1:
=copy(temp,1,index-1);{取得说话人姓名}
temp2:
=copy(temp,index+2,length(temp)-index-1);{取得证词}
if(temp2='Iamguilty.')or(temp2='Iamnotguilty.')then
forj:
=1tomdo
ifname[j]=temp1thenbegin
inc(total1);{total1表示第一类证词的总数}
witness10[total1]:
=j;{记下第一类第total1条证词的说话人的序号}
witness1[total1]:
=temp2;{记下第一类第total1条证词}
break;
end;
if(pos('isguilty.',temp2)>0)or(pos('isnotguilty.',temp2)>0)thenbegin
temp0:
=copy(temp2,1,pos('is',temp2)-1);{取得证词的叙述对象(主语)}
flag:
=false;
fork:
=1tomdo
iftemp0=name[k]thenbegin
flag:
=true;
break;
end;
ifflagthen{如果证词的叙述对象(主语)确实存在}
forj:
=1tomdo
ifname[j]=temp1thenbegin{记入到第一类证词中}
inc(total1);
witness10[total1]:
=j;
witness1[total1]:
=temp2;
break;
end;
end;
flag:
=false;
forj:
=1to7do
iftemp2='Todayis'+dow[j]thenbegin
flag:
=true;
break;
end;
ifflagthen{如果证词是关于星期的判断}
forj:
=1tomdo
ifname[j]=temp1thenbegin{记入到第二类证词中}
inc(total2);{total2表示第二类证词的总数}
witness20[total2]:
=j;{记下第二类第total2条证词的说话人的序号}
witness2[total2]:
=temp2;{记下第二类第total2条证词}
break;
end;
end;
close(f1);
resolution:
=0;{resolution表示解的个数}
fori:
=1tomdobegin{穷举,第i个人为罪犯}
ifresolution>1thenbreak;{如果解的个数多于1个,则跳出循环}
fillchar(truth,sizeof(truth),0);{诚信记录赋初值为0,表示此人尚无有效证词}
forj:
=1tototal1dobegin{逐条处理第一类证词}
ifwitness1[j]='Iamguilty.'thenbegin{如果证词为Iamguilty.}
ifi=witness10[j]then{如果说话人就是罪犯,则本证词为真}
casetruth[i]of
0:
truth[i]:
=1;{如果此人的诚信记录为0,则此人说真话(记为1)}
2:
truth[i]:
=3;{如果此人的诚信记录为2(即以前说假话),则此人自相矛盾(记为3)}
end
else{如果说话人不是罪犯,则本证词为假}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=2;{如果此人的诚信记录为0,则此人说假话(记为2)}
1:
truth[witness10[j]]:
=3;{如果此人的诚信记录为1(即以前说真话),则此人自相矛盾(记为3)}
end;
end;
ifwitness1[j]='Iamnotguilty.'thenbegin{如果证词为Iamnotguilty.}
ifi=witness10[j]then{如果说话人是罪犯,则本证词为假}
casetruth[i]of
0:
truth[i]:
=2;
1:
truth[i]:
=3;
end
else{如果说话人不是罪犯,则本证词为真}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=1;
2:
truth[witness10[j]]:
=3;
end;
end;
if(pos('isguilty.',witness1[j])>0)thenbegin{如果证词含有isguilty.}
temp:
=copy(witness1[j],1,pos('isguilty.',witness1[j])-1);{取得证词的主语}
ifname[i]=tempthen{如果证词的主语是罪犯,则本证词为真}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=1;
2:
truth[witness10[j]]:
=3;
end
else{如果证词的主语不是罪犯,则本证词为假}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=2;
1:
truth[witness10[j]]:
=3;
end;
end;
if(pos('isnotguilty.',witness1[j])>0)thenbegin{如果证词含有isnotguilty.}
temp:
=copy(witness1[j],1,pos('isnotguilty.',witness1[j])-1);{取得证词的主语}
ifname[i]=tempthen{如果证词的主语是罪犯,则本证词为假}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=2;
1:
truth[witness10[j]]:
=3;
end
else{如果证词的主语不是罪犯,则本证词为真}
casetruth[witness10[j]]of
0:
truth[witness10[j]]:
=1;
2:
truth[witness10[j]]:
=3;
end;
end;
end;{第一类证词全部处理完毕}
iftotal2>0thenbegin{如果有第二类证词存在,处理第二类证词}
fork:
=1tomdotruth0[k]:
=truth[k];{记下第一类证词全部处理完毕后的诚信记录}
forweekday:
=1to7dobegin{穷举,今天是星期日,星期一直到星期六}
fork:
=1tomdotruth[k]:
=truth0[k];{诚信记录还原为第一类证词全部处理完毕后的诚信记录}
forj:
=1tototal2do{逐条处理第二类证词}
ifpos(dow[weekday],witness2[j])>0then{如果证词中含有当前穷举的星期值,则本证词为真}
casetruth[witness20[j]]of
0:
truth[witness20[j]]:
=1;
2:
truth[witness20[j]]:
=3;
end
else{如果证词中不含有当前穷举的星期值,则本证词为假}
casetruth[witness20[j]]of
0:
truth[witness20[j]]:
=2;
1:
truth[witness20[j]]:
=3;
end;
p1:
=0;p2:
=0;p3:
=0;
fork:
=1tomdoiftruth[k]=1theninc(p1);{p1表示始终说真话的人的总数}
fork:
=1tomdoiftruth[k]=2theninc(p2);{p2表示始终说假话的人的总数}
fork:
=1tomdoiftruth[k]=3theninc(p3);{p3表示说过自相矛盾的话的人的总数}
if(p1<=m-n)and(p2<=n)and(p3=0)thenbegin{如果说真话的人的总数小于等于m-n且说假话的人的总数小于等于n且没有人说过自相矛盾的话,那么当前罪犯i就是本题的一个解}
name0:
=name[i];{记下罪犯的姓名}
inc(resolution);{解的个数增1}
break;{退出星期的穷举}
end;
end;{星期的穷举完毕}
end;{第二类证词处理完毕}
p1:
=0;p2:
=0;p3:
=0;
fork:
=1tomdoiftruth[k]=1theninc(p1);
fork:
=1tomdoiftruth[k]=2theninc(p2);
fork:
=1tomdoiftruth[k]=3theninc(p3);
if(p1<=m-n)and(p2<=n)and(p3=0)and(name0<>name[i])thenbegin{为避免重复计解,此处多加了一个条件:
name0<>name[i]}
name0:
=name[i];
inc(resolution);
end;
end;
ifresolution=1thenwriteln(f2,name0);{如果只有一个解,则输出罪犯姓名}
ifresolution=0thenwriteln(f2,'Impossible');{如果无解,则输出Impossible}
ifresolution>1thenwriteln(f2,'CannotDetermine');{如果有多个可能解,则输出CannotDetermine}
close(f2);
end.
[点评]基本题,比较复杂,重点考查参赛者的字符串运算和逻辑运算,逻辑推理能力。
难点主要在于如何处理关于星期的证词,以及证词之间是否存在矛盾。
题三加分二叉树
【问题描述】
设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。
每个节点都有一个分数(均为正整数),记第i个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分×subtree的右子树的加分+subtree的根的分数
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。
不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。
要求输出;
(1)tree的最高加分
(2)tree的前序遍历
【输入格式】
第1行:
一个整数n(n<30),为节点个数。
第2行:
n个用空格隔开的整数,为每个节点的分数(分数<100)。
【输出格式】
第1行:
一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:
n个用空格隔开的整数,为该树的前序遍历。
【输入样例】
5
571210
【输出样例】
145
31245
[分析]很显然,本题适合用动态规划来解。
如果用数组value[i,j]表示从节