NOIP提高组试题及解析.docx
《NOIP提高组试题及解析.docx》由会员分享,可在线阅读,更多相关《NOIP提高组试题及解析.docx(12页珍藏版)》请在冰豆网上搜索。
NOIP提高组试题及解析
NOIP2007提高组试题及解析
1.统计数字(count.pas/c/cpp)
【问题描述】
某次科研调查时得到了n个自然数,每个数均不超过(1.5*109)。
已知不相同的数不超过10000个,现在需要统计这些自然数各自出现的次数,并按照自然数从小到大的顺序输出统计结果。
【输入】
输入文件count.in包含n+1行;
第一行是整数n,表示自然数的个数;
第2~n+1每行一个自然数。
【输出】
输出文件count.out包含m行(m为n个自然数中不相同数的个数),按照自然数从小到大的顺序输出。
每行输出两个整数,分别是自然数和该数出现的次数,其间用一个空格隔开。
【输入输出样例】
count.in
8
2
4
2
4
5
100
2
100
count.out
23
42
51
1002
【限制】
40%的数据满足:
1<=n<=1000
80%的数据满足:
1<=n<=50000
100%的数据满足:
1<=n<=,每个数均不超过1500000000(1.5*109)
【解题思想1】显然,此题可以用排序的方法来解决,根据n的范围,可以看出,O(nlogn)的算法是可以接受的。
【解题思想2】维护一个二叉树,以数的大小作为节点的权值,以数的重复次数作为节点的附加信息。
之后中序遍历即可。
看起来,树内的节点个数应该不到n,所以可能表现不错,其算法复杂度依然为O(nlogn)
【实际数据规模】挺小的,而且也没有传说中的卡Qsort的数据,全部都很温柔。
【分析】这个题目实在不能说是一道TG组的好题。
实际上,个人认为题目最大的意义在于:
提供了10个测试排序算法的不怎么特别好的数据。
话说回来,此题是送分题,但是送分题送的这么水,考察的也就只有OIers的细心程度了。
在考试的时候,要相信有简单的题目,要相信有直接的算法。
在我的身边就有几个同学因为这个题目与一等失之交臂,这是最可惜的事情。
vara:
array[1..]oflongint;
i,j,k,m,n:
longint;
procedureqsort(s,t:
longint);
vari,j,mid,temp:
longint;
begin
i:
=s;j:
=t;mid:
=a[(s+t)div2];
whilei<=jdo
begin
whilea[i] whilea[j]>middodec(j);
ifi<=jthen
begin
temp:
=a[i];a[i]:
=a[j];a[j]:
=temp;
inc(i);dec(j);
end;
end;
ifiifj>sthenqsort(s,j);
end;
begin
readln(n);
fori:
=1tondoreadln(a[i]);
qsort(1,n);
a[n+1]:
=maxlongint;
k:
=1;
fori:
=2ton+1do
ifa[i]<>a[i-1]then
beginwriteln(a[i-1],'',k);k:
=1;end
elsek:
=k+1;
end.
2.字符串的展开(expand.pas/c/cpp)
【问题描述】
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:
如果在输入的字符串中,含有类似于“d-h”或者“4-8”的字串,我们就把它当作一种简写,输出时,用连续递增的字母获数字串替代其中的减号,即,将上面两个子串分别输出为“defgh”和“45678”。
在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。
具体约定如下:
(1)遇到下面的情况需要做字符串的展开:
在输入的字符串中,出现了减号“-”,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。
(2)参数p1:
展开方式。
p1=1时,对于字母子串,填充小写字母;p1=2时,对于字母子串,填充大写字母。
这两种情况下数字子串的填充方式相同。
p1=3时,不论是字母子串还是数字字串,都用与要填充的字母个数相同的星号“*”来填充。
(3)参数p2:
填充字符的重复个数。
p2=k表示同一个字符要连续填充k个。
例如,当p2=3时,子串“d-h”应扩展为“deeefffgggh”。
减号两边的字符不变。
(4)参数p3:
是否改为逆序:
p3=1表示维持原来顺序,p3=2表示采用逆序输出,注意这时候仍然不包括减号两端的字符。
例如当p1=1、p2=2、p3=2时,子串“d-h”应扩展为“dggffeeh”。
(5)如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:
“d-e”应输出为“de”,“3-4”应输出为“34”。
如果减号右边的字符按照ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:
“d-d”应输出为“d-d”,“3-1”应输出为“3-1”。
【输入】
输入文件expand.in包括两行:
第1行为用空格隔开的3个正整数,一次表示参数p1,p2,p3。
第2行为一行字符串,仅由数字、小写字母和减号“-”组成。
行首和行末均无空格。
【输出】
输出文件expand.out只有一行,为展开后的字符串。
【输入输出样例1】
expand.in
121
expand.out
abcs-w1234-9s-4zzabcsttuuvvw89s-4zz
【输入输出样例2】
expand.in
232
expand.out
a-d-daCCCBBBd-d
【输入输出样例3】
expand.in
342
expand.out
di-jkstra2-6dijkstra2************6
【限制】
40%的数据满足:
字符串长度不超过5
100%的数据满足:
1<=p1<=3,1<=p2<=8,1<=p3<=2。
字符串长度不超过100
解题思路:
也是个水题,考察对语言的运用能力,字符串处理的技巧和仔细读题的能力,测试数据已经包括所有情况了,但是有些细节要想清楚,而且一些特殊的情况也要单独测试一下,如9-a-a,--a-1;
需要注意的是:
首尾‘-’的情况,要用ansistring
Varp1,p2,p3,ii,jj,i,ch1,ch2:
longint;
s:
string;
procedureprint(st,en:
longint);
begin
ifp3=1then
forii:
=sttoendobegin
forjj:
=1top2dowrite(chr(ii));
end
else
forii:
=endowntostdobegin
forjj:
=1top2dowrite(chr(ii));
end;
end;
procedureprint0(st,en:
longint);
begin
forii:
=sttoendo
forjj:
=1top2dowrite('*');
end;
procedurejudge;
begin
ch1:
=ord(s[i-1]);
ch2:
=ord(s[i+1]);
ifch1 if(ch1>=48)and(ch1<=57)and(ch2>=48)and(ch2<=57)thenbegin
ifp1=3thenprint0(ch1+1,ch2-1)elseprint(ch1+1,ch2-1);
exit;
end;
if(ch1>=97)and(ch1<=122)and(ch2>=97)and(ch2<=122)thenbegin
casep1of
1:
print(ch1+1,ch2-1);
2:
print(ch1-31,ch2-33);
3:
print0(ch1+1,ch2-1);
end;
exit;
end;
end;
write('-');
end;
begin
readln(p1,p2,p3);readln(s);write(s[1]);
fori:
=2tolength(s)-1do
ifs[i]='-'thenjudge
elsewrite(s[i]);
writeln(s[length(s)]);
end.
3.矩阵取数游戏(game.pas/c/cpp)
【问题描述】帅帅经常更同学玩一个矩阵取数游戏:
对于一个给定的n*m的矩阵,矩阵中的每个元素aij据为非负整数。
游戏规则如下:
1.每次取数时须从每行各取走一个元素,共n个。
m次后取完矩阵所有的元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和;每行取数的得分=被取走的元素值*2i,其中i表示第i次取数(从1开始编号);
4.游戏结束总得分为m次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
【输入】
输入文件game.in包括n+1行;
第一行为两个用空格隔开的整数n和m。
第2~n+1行为n*m矩阵,其中每行有m个用单个空格隔开
【输出】
输出文件game.out仅包含1行,为一个整数,即输入矩阵取数后的最大的分。
【输入输出样例1】
game.in
23
124
342
game.out
82
【输入输出样例1解释】
第1次:
第一行取行首元素,第二行取行尾元素,本次的氛围1*21+2*21=6
第2次:
两行均取行首元素,本次得分为2*22+3*22=20
第3次:
得分为3*23+4*23=56。
总得分为6+20+56=82
【输入输出样例2】
game.in
14
4505
game.out
122
【输入输出样例3】
game.in
210
96565446861223888043
16951829305388836467
game.out
【限制】
60%的数据满足:
1<=n,m<=30,答案不超过1016
100%的数据满足:
1<=n,m<=80,0<=aij<=1000
解题思路:
很多人可能会被第一个样例误导认为贪心,第二个样例就提示我们贪心是错误的,其实有经验的选手一看就是dp,可以看作类似于石子归并,每次在2边进行合并我们可以把问题转化为每次要取完一整行,一共取m次。
注意到每一行都是独立的一个结构,所以可以每行的结果计算出来后求和。
对于每一行,f(i,j)表示从i到j这一子段单独操作可以达到的最大权值。
有状态转移方程:
f(i,j)=2*max(a(i)+f(i+1,j),a(j)+f(i,j-1))
边界f(i,i)=2*a(i)
最后答案就把每一行的f(0,m-1)加起来即可。
这里可以算得答案不超过30位10进制数,所以高精度的digit数组开到30足够了。
乘法也不用写,用两次加法即可。
Constmaxn=80;one=10000;
Typenum=array[0..25]oflongint;
Vara:
array[1..maxn]oflongint;f:
array[1..maxn,1..maxn]ofnum;
m,n:
longint;ans:
num;
procedureadd(varc:
num;a,b:
num);
vari,x:
longint;
begin
ifa[0]>b[0]thenc[0]:
=a[0]elsec[0]:
=b[0];x:
=0;
fori:
=1toc[0]do
begin
x:
=a[i]+b[i]+x;
c[i]:
=xmodone;
x:
=xdivone;
end;
ifx>0thenbegininc(c[0]);c[c[0]]:
=x;end;
end;
proceduretime(vara:
num;k:
longint);
vari,x:
longint;
begin
x:
=0;
fori:
=1toa[0]dobeginx:
=a[i]*k+x;a[i]:
=xmodone;x:
=xdivone;end;
whilex>0dobegininc(a[0]);a[a[0]]:
=xmodone;x:
=xdivone;end;
end;
functioncompare(a,b:
num):
boolean;
vari:
longint;
begin
ifa[0]>b[0]thenexit(true);
ifb[0]>a[0]thenexit(false);
fori:
=a[0]downto1do
begin
ifa[i]>b[i]thenexit(true);
ifb[i]>a[i]thenexit(false);
end;
exit(false);
end;
procedureplus(varc:
num;a:
num;k:
longint);
vari,j:
longint;
begin
c:
=a;i:
=1;inc(c[i],k);
whilec[i]>=onedo
begin
c[i+1]:
=c[i+1]+c[i]divone;
c[i]:
=c[i]modone;
inc(i);
ifc[0]=i;
end;
end;
proceduredp;
vari,j:
longint;max,temp:
num;
begin
fillchar(f,sizeof(f),0);
fori:
=1tomdobeginf[i,i][0]:
=1;f[i,i][1]:
=a[i]*2;end;
fori:
=m-1downto1do
forj:
=i+1tomdo
begin
plus(max,f[i+1,j],a[i]);
time(max,2);
plus(temp,f[i,j-1],a[j]);
time(temp,2);
ifcompare(temp,max)thenmax:
=temp;
f[i,j]:
=max;
end;
add(ans,ans,f[1,m]);
end;
procedureinit;
vari,j:
longint;
begin
readln(n,m);
fori:
=1tondobeginforj:
=1tomdoread(a[j]);readln;dp;end;
end;
procedureouts;
vari:
longint;
begin
write(ans[ans[0]]);
fori:
=ans[0]-1downto1do
begin
write(ans[i]div1000mod10);
write(ans[i]div100mod10);
write(ans[i]div10mod10);
write(ans[i]mod10);
end;
end;
begin
init;outs;
end.
4.树网的核(core.pas/c/cpp)
【问题描述】设T=(V,E,W)是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称T为树网(treebetwork),其中V,E分别表示结点与边的集合,W表示各边长度的集合,并设T有n个结点。
路径:
树网中任何两结点a,b都存在唯一的一条简单路径,用d(a,b)表示以a,b为端点的路径的长度,它是该路径上各边长度之和。
我们称d(a,b)为a,b两结点间的距离。
D(v,P)=min{d(v,u),u为路径P上的结点}。
树网的直径:
树网中最长的路径成为树网的直径。
对于给定的树网T,直径不一定是唯一的,但可以证明:
各直径的中点(不一定恰好是某个结点,可能在某条边的内部)是唯一的,我们称该点为树网的中心。
偏心距ECC(F):
树网T中距路径F最远的结点到路径F的距离,即
ECC(F)=max{d(v,F),v∈V}
任务:
对于给定的树网T=(V,E,W)和非负整数s,求一个路径F,他是某直径上的一段路径(该路径两端均为树网中的结点),其长度不超过s(可以等于s),使偏心距ECC(F)最小。
我们称这个路径为树网T=(V,E,W)的核(Core)。
必要时,F可以退化为某个结点。
一般来说,在上述定义下,核不一定只有一个,但最小偏心距是唯一的。
下面的图给出了树网的一个实例。
图中,A-B与A-C是两条直径,长度均为20。
点W是树网的中心,EF边的长度为5。
如果指定s=11,则树网的核为路径DEFG(也可以取为路径DEF),偏心距为8。
如果指定s=0(或s=1、s=2),则树网的核为结点F,偏心距为12。
【输入】
输入文件core.in包含n行:
第1行,两个正整数n和s,中间用一个空格隔开。
其中n为树网结点的个数,s为树网的核的长度的上界。
设结点编号以此为1,2,……,n。
从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。
例如,“247”表示连接结点2与4的边的长度为7。
所给的数据都是争取的,不必检验。
【输出】
输出文件core.out只有一个非负整数,为指定意义下的最小偏心距。
【输入输出样例】
【输入输出样例1】
core.in
52
125
232
244
253
Core.out
5
【输入输出样例2】
core.in
86
132
232
346
453
464
472
783
core.out
5
【限制】
40%的数据满足:
5<=n<=15
70%的数据满足:
5<=n<=80
100%的数据满足:
5<=n<=300,0<=s<=1000。
边长度为不超过1000的正整数
解题思路:
可以证明的是,如果图中存在多条路径,则在任何一条直径上都存在一条core(反证法,用到直径的距离最大性)。
因此只需讨论一条直径上的core的情况即可。
接着,如果一条路径包括了一条子路径的所有边,那么子路径的解不会比父路径更优。
这一点后面将用到。
下面是算法:
先寻找一条直径:
任取一点u',遍历得到到它的最远点u,再对u寻找一个到它的最远点v,则路径u-v一定是一条直径(想想为什么),在刚才遍历u-v的时候记录遍历到每一个点的时候的前继,那么从v出发一直寻找前继到u,就能记录下这一条直径。
这里复杂度是O(n)。
然后枚举core:
注意刚才说到了路径是越长越好,因此枚举的时候,每枚举一个初始点,向后都尽量延伸到不超过s的距离。
这样每个初始点只确定一个枚举对象。
对于终止点的选定可以采用一个游标的方式,当初始点从u到v扫过一遍时,游标也从u扫到了v(回想怎么求解凸多边形最远点对的),因此这里复杂度是O(n)。
最后求ECC:
暂时删除枚举对象(那条路径)上的所有边,然后以那条路径上的那些顶点作为源点开始遍历图,找到的最大距离就是该路径的ECC。
这里复杂度也将是O(n)。
因此总复杂度为O(n)+O(n)*O(n)=O(n^2),ac。
线性算法:
同样只要考虑一条直径。
先对于直径上的顶点赋值:
与该顶点连通的最远点的距离。
这样可以构成一个线性模型,点(记为v[i])有一个值(记为f[i]),边有一个值(即原来图中v[i]和v[i+1]之间的距离,记为e[i])。
这一步可以O(n)完成。
接着考虑这个线性模型。
枚举过程还是和刚才一样。
假设枚举的是v[a]到v[b]这一段路径,那么,这一段路径的ECC应当是以下几个值的最大值:
1)max(v[a],v[a+1]...,v[b]);2)max(v[i]+e[i]+e[i+1]+...+e[a-1],ib);
如果以上3个值能够在O
(1)内回答,那么总复杂度就将是O(n)
对于1,显然这是个RMQ模型,有一个O(n)算法(当然st的O(nlogn)也可以接受)。
或者考虑到这个问题的特殊性,所有(a,b)类的询问区间都不是严格包含的(即任取询问(a,b)(c,d)都不存在a>b&&c 对于2,设一个数组a[1..n],a[k]表示max(v[i]+e[i]+...+e[k-1],i 3和2同理,不再详述
Constmaxn=65536;
Varn,s,i,j,aa,bb,k,temp,st,en,ans,tou,max,len,dep,now:
longint;
w,f,mid:
array[0..300,0..300]oflongint;
p:
array[0..50000,1..2]ofinteger;
a:
array[0..300]ofinteger;
d:
array[0..300]oflongint;
begin
readln(n,s);
fori:
=1tondo
begin
w[i,i]:
=0;f[i,i]:
=0;
forj:
=i+1tondo
begin
f[