Noip2014解题报告
Noip2014普及组解题报告
--slzxxjd 注:数据分析中的数据均为ZJ评测结果
1.珠心算测验(count.cpp/c/pas)
【问题描述】
珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。
某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正整数集合,集合中的数各不相同,然后要求学生回答:其中有多少个数,恰好等于集合中另外两个(不同的)数之和?
最近老师出了一些测验题,请你帮忙求出答案。【输入】
输入文件名为count.in。
输入共两行,第一行包含一个整数n,表示测试题中给出的正整数个数。第二行有n 个正整数,每两个正整数之间用一个空格隔开,表示测试题中给出的正整数。
【输出】
输出文件名为count.out。
输出共一行,包含一个整数,表示测验题答案。【输入输出样例】【样例说明】
由1+2=3,1+3=4,故满足测试要求的答案为2。注意,加数和被加数必须是集合中的
两个不同的数。
【数据说明】
对于100%的数据,3≤n≤100,测验题给出的正整数大小不超过10,000。【题目分析】
对于一个正整数集合,求出这个正整数集合中有多少个数恰好等于集合中另外两个(不同的)数之和。
【算法分析】
继承了历年Noip 的传统,第一题都是模拟题。这道题目的算法不难发现。
我们可以用O(n)的方法确定这个正整数集合中的其中一个数,然后用O(n 2
)
count.in count.out 4
1234
2
算法找出另外两个不同的数(注意了,题目强调了不同的!尽管这个细节不注意连连样例都不会过的~)。非常值得注意的是,很多同学这道简单的模拟题仅仅只拿了30分,问题出在审题不清,题目叙述中要求求的是有几个数恰好等于集合中另外两个数之和,也就是说如果一个数恰好等于集合中不同的两(或者两组以上)组数,那么这样的只能算作一种。
【Code】
01var
02i,j,k,m,n,s,t,ans:longint;
03a:array[1..1000]of longint;
04begin
05assign(input,'count.in');
06assign(output,'count.out');
07reset(input);
08rewrite(output);
09read(n);
10for i:=1to n do
11read(a[i]);
12ans:=0;
13for i:=1to n do
14begin
15t:=0;
16for j:=1to n do
17begin
18for k:=1to n do
19if(i<>j)and(j<>k)and(i<>k)then
20if a[j]+a[k]=a[i]then begin t:=1;break;end;
21if t=1then break;
22end;
23if t=1then inc(ans);
24end;
25writeln(ans);
26close(input);
27close(output);
28end.
【数据分析】
这道题目的平均分是56.6分,那么也就是一半以上的人这道题目还是没有出现审题错误的,只是算法上出现了一些其他的Bug。
30分,审题错误。
本题100分共有173人。
其余非100分的分值为一些其他的Bug,不再细细分析。
【总结】
模拟
2.比例简化
(ratio.cpp/c/pas)
【问题描述】
在社交媒体上,经常会看到针对某一个观点同意与否的民意调查以及结果。例如,对某一观点表示支持的有1498人,反对的有902人,那么赞同与反对的比例可以简单的记为1498:902。
不过,如果把调查结果就以这种方式呈现出来,大多数人肯定不会满意。因为这个比例的数值太大,难以一眼看出它们的关系。对于上面这个例子,如果把比例记为5:3,虽然与真实结果有一定的误差,但依然能够较为准确地反映调查结果,同时也显得比较直观。
现给出支持人数A,反对人数B,以及一个上限L,请你将A比B化简为A’比B’,要求在A’和B’均不大于L且A’和B’互质(两个整数的最大公约数是1)的前提下,A’/B’≥A/B且A’/B’-A/B的值尽可能小。
【输入】
输入文件名为ratio.in。
输入共一行,包含三个整数A,B,L,每两个整数之间用一个空格隔开,分别表示支持
人数、反对人数以及上限。
【输出】
输出文件名为ratio.out。
输出共一行,包含两个整数A’,B’,中间用一个空格隔开,表示化简后的比例。
【输入输出样例】
ratio.in ratio.out
14989021053
【数据说明】
对于100%的数据,1≤A≤1,000,000,1≤B≤1,000,000,1≤L≤100,A/B ≤L。
【题目分析】
将一组比例在L的范围内化简成最接近原比例的最简比例。
【算法分析】
在考场上初看到这道题目,笔者与大家一样感到很茫然。但是Pj的题目还是比较仁慈的,尤其是才第二道题目。考虑到这道题目的数据范围L仅仅才一百,所以我们可以采用暴力枚举的方法来解决这道题目。
当时,走出考场,有好多人问题用real(Pascal)或者float(C++),会
不会在比较上出现精度问题,开始自己用了real也就没有下定论。后来想了想,理论证明单精度实型变量也是不会出现精度问题的:题目中A、B的范围最多只有6位,众所周知,所谓精度出问题时因为出现了循环节超出了实型变量本身的精度范围,才会出现问题的。然后还有一个引理就是说,循环小数转化为分数的话,分母就是由循环节的位数个9组成的,那么按照题目的数据范围,循环节最多有就只有5位,故单精度实型变量不会出现精度问题。
至于上面的说法,下面介绍两种方法解决这道题目:
算法○1,单精度实型变量比较法:这种方法用很裸的O(n2)的方法就能解决,上文已经有过叙述,不再在这里详解。
算法○2,通分比较法:将比例式的第二比例项通过通分转换,是分母一样,找出分值最接近的一个即可。
然后这道题目细节问题比较多,对于题目要求求出的比例式的要求要仔细阅读,认真编写!其次一个最要注意的问题,一定要对你找出的一个比例式判断是都为最简比例式。
【Code】
算法○1:
01var
02i,j,k,m,n,s,t,l,x,y:longint;
03min:real;
04function gcd(x,y:longint):longint;
05var r:longint;
06begin
07r:=x mod y;
08while r<>0do
09begin
10x:=y;
11y:=r;
12r:=x mod y;
13end;
14exit(y);
15end;
16begin
17assign(input,'ratio.in');
18assign(output,'ratio.out');
19reset(input);
20rewrite(output);
21read(n,m,l);
22x:=0;y:=0;min:=1e30;
23for i:=1to l do
24for j:=1to l do
25if(i/j>=n/m)then
26if gcd(i,j)=1then
27if(i/j-n/m 28writeln(x,'',y); 29close(input); 30close(output); 31end. 算法○2: 01var 02i,j,k,a,b,l,mi,mj,t:longint; 03function pp(x,y:longint):boolean; 04begin 05t:=x mod y; 06while t>0do 07begin 08x:=y;y:=t;t:=x mod y 09end; 10if y=1then exit(true)else exit(false) 11end; 12begin 13assign(input,'ratio.in'); 14assign(output,'ratio.out'); 15reset(input); 16rewrite(output); 17read(a,b,l); 18for i:=1to l do 19for j:=1to l do 20if pp(i,j)then 21if i*b>=a*j then 22if(i*mj 23begin 24mi:=i; 25mj:=j 26end; 27writeln(mi,'',mj); 28close(input); 29close(output) 30end. 【数据分析】 这道题目的平均分是56.6分,平均分竟然与第一题一样。 60分,slzxzhl就因为这道题目审题有误而与满分失之交臂。 本题100分共有195人。 这道题目考察了大家的最基础阅读和编写能力,细节较多,不再对数据进行分析。 【总结】 模拟 3.螺旋矩阵 (matrix.cpp/c/pas) 【问题描述】 一个n行n列的螺旋矩阵可由如下方法生成: 从矩阵的左上角(第1行第1列)出发,初始时向右移动;如果前方是未曾经过的格子,则继续前进,否则右转;重复上述操作直至经过矩阵中所有格子。根据经过顺序,在格子中依次填入1,2,3,...,n2,便构成了一个螺旋矩阵。 下图是一个n=4时的螺旋矩阵。 1234 1213145 1116156 10987 现给出矩阵大小n以及i和j,请你求出该矩阵中第i行第j列的数是多少。 【输入】 输入文件名为matrix.in。 输入共一行,包含三个整数n,i,j,每两个整数之间用一个空格隔开,分别表示矩阵大小、待求的数所在的行号和列号。 【输出】 输出文件名为matrix.out。 输出共一行,包含一个整数,表示相应矩阵中第i行第j列的数。 【输入输出样例】 matrix.in matrix.out 42314 【数据说明】 对于50%的数据,1≤n≤100; 对于100%的数据,1≤n≤30,000,1≤i≤n,1≤j≤n。 【题目分析】 对于n,求出边长为n的螺旋矩阵中第i行第j列的数。 【算法分析】 这道题目又是一道模拟题(最近好像Noip比较喜欢模拟题~)。看到数据范围,笔者首先想的是允许的时间复杂度是多少,显然O(n2)不仅时间过不了,空间也会爆掉,这时我们就必须开始寻找新的方法。 这种输入只有特定的几个数,输出也只有一个特定的数的题目。小数据的题目,可能有很多同学会选择打表。对于这道题目,我们不妨从数学角度考虑一下:首先,我能把一个螺旋矩阵,从外到内一次分成(n+1)/2层。显然,我们可以先用数学方法直接判断出,给定的(i,j)在第几层。此前,说明以下:引理1:给定的i,j,我们算出在第几层,我们可以首先分别算出按照i和按照j应该处于第几层,然后出两个中较小的一个作为(i,j)所处的层数。 引理2:已经分开的n层,第i层的元素个数为(n-3*(i-1))*4,当n 为奇数,且i为最里面一层时,进行特判为1。 引理3:可以根据i,j算出在第几层的过程得出,(i,j)属于靠上的横行、靠下的横行、靠左的竖行、靠右的竖行四类。 有了上面的三个引理,我们就可以首先用O(n)的算法将(i,j)所在层数-1的所有元素个数加起来,然后在算出(i,j)在其所处的层数中是第几个元素,最后加起来,即为(i,j)位置上的数。 【Code】 01var 02i,j,k,m,n,s,t,x,y:longint; 03function min(a,b:longint):longint; 04begin 05if a 06end; 07function hang(x:longint):longint; 08begin 09if x<=n div2then exit(x)else exit(n-x+1); 10end; 11function lie(x:longint):longint; 12begin 13if x<=n div2then exit(x)else exit(n-x+1); 14end; 15function num(x,y:longint):longint; 16begin 17if y=(x+1)div2then 18begin 19if x mod2=1then exit(1) 20else exit((x-1-2*(y-1))*4); 21end 22else exit((x-1-2*(y-1))*4); 23end; 24function pp(m:longint):longint; 25begin 26if x<=n div2then 27begin 28if m=x then exit(1); 29end 30else 31begin 32if m=n-x+1then exit(3); 33end; 34if y<=n div2then 35begin 36if m=y then exit(4); 37end 38else 39begin 40if m=n-y+1then exit(2); 41end; 42end; 43begin 44assign(input,'matrix.in'); 45assign(output,'matrix.out'); 46reset(input); 47rewrite(output); 48read(n,x,y); 49m:=min(hang(x),lie(y)); 50s:=0; 51for i:=1to m-1do 52s:=s+num(n,i); 53k:=pp(m); 54if k=1then writeln(y-m+1+s) 55else if k=2then writeln(x-m+1+s+(n-1-2*(m-1))) 56else if k=3then writeln((n-m+1)-y+1+s+(n-1-2*(m-1))*2) 57e lse if k=4then writeln((n-m+1)-x+1+s+(n-1-2*(m-1))*3); 58close(input); 59close(output); 60end. 【数据分析】 这道题目的平均分是52.0分,较第一二题,明显有下降。 本题100分共有148人。 50分,用空间复杂度O(n2)的算法直接模拟即可获得50分。 【总结】 数学、分治、模拟 4.子矩阵 (submatrix.cpp/c/pas) 【问题描述】 给出如下定义: 1.子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。 例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个2*3的子矩阵如右图所示。 的其中一个2*3的子矩阵是 2.相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。 3.矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。 本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r 行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。 【输入】 输入文件名为submatrix.in。 第一行包含用空格隔开的四个整数n,m,r,c,意义如问题述中所述,每两个整数之间用一个空格隔开。 接下来的n行,每行包含m个用空格隔开的整数,用来表示问题述中那个n行m列的矩阵。 【输出】 输出文件名为submatrix.out。 输出共1行,包含1个整数,表示满足题目述的子矩阵的最小分值。 【输入输出样例1】 submatrix.in submatrix.out 5523 6 93339 94874 17466 68569 74561 【输入输出样例1说明】 该矩阵中分值最小的2行3列的子矩阵由原矩阵的第4行、第5行与第1列、第3列、第4列交叉位置的元素组成,为,其分值为|6?5|+|5?6|+|7?5|+|5?6|+|6?7|+|5?5|+|6?6|=6。【输入输出样例2】submatrix.in submatrix.out 7733 7776210558821622955617793617819147881059118101315486 16 【输入输出样例2说明】 该矩阵中分值最小的3行3列的子矩阵由原矩阵的第4行、第5行、第6行与第2列、第6列、第7列交叉位置的元素组成,选取的分值最小的子矩阵为 10 85889879【数据说明】 对于50%的数据,1≤12,1≤12,矩阵中的每个元素1≤a[i][j]≤20;对于100%的数据,1≤16,1≤16,矩阵中的每个元素1≤a[i][j]≤1000,1≤r≤n,1≤c≤m。 【题目分析】 对于总的矩阵,输出这个矩阵中分值最小的子矩阵。 【算法分析】 首先看到这道题目,最为Pj 的最后一题了吧,应该是一道很复杂的DP 题。但是看到数据范围的那一刻,相信不只笔者一个人,大多数人都笑了,竟然是一道搜索题。 搜索最初的雏形,显然是O(2*2n )的深搜,这种方法,即为枚举整个矩阵的每行每列取与不取这两种情况,比较简单又容易操作,笔者这里不在展开。 这道题目的应该可以说是标算吧,粗粗浏览了一遍ZJ 全过的几个人中都是这种优化方法(其中一个人是记忆化搜索模拟DP)。我们首先可以先暴力枚举行的取舍(由于行的取舍和列的取舍本质上完全相同这里就不再多说),然后我们可以用一种比较普遍容易想到的DP 方法优化列的取舍。DP 是O(n 4)的优化, 657656 只需用前缀和的优化就可以很方便的解决,这里也就不再具体细说,详见Code。 【Code】 01var 02i,j,k,m,n,s,t,x,y,min,i1,i2,i3,i4:longint; 03a:array[1..20,1..20]of longint; 04b,e:array[1..20]of longint; 05c:array[1..20,1..20]of longint; 06d:array[1..20,1..20]of longint; 07procedure qq; 08var i,j,k,p,t:longint; 09begin 10fillchar(c,sizeof(c),$7f); 11for i:=1to m-1do 12for j:=i+1to m do 13begin 14d[i,j]:=0; 15for k:=1to n do 16if b[k]=1then d[i,j]:=d[i,j]+abs(a[k,i]-a[k,j]); 17end; 18for i:=1to m do 19begin 20t:=0;e[i]:=0; 21for j:=1to n do 22if b[j]=1then 23begin 24if t=0then t:=j 25else 26begin 27e[i]:=e[i]+abs(a[j,i]-a[t,i]); 28t:=j; 29end; 30end; 31end; 32for i:=1to m do 33c[i,0]:=0; 34for i:=1to m do 35for j:=1to i do 36if j 37begin 38for k:=0to j do 39if c[j,k]+d[j,i]+e[i] 40end 41else if j=i then 42begin 43k:=0; 44if c[j,k]+d[j,i]+e[i] 45end; 46p:=maxlongint; 47for i:=y to m do 48if c[i,y]<=p then p:=c[i,y]; 49if p 50end; 51procedure pp(f:longint); 52var i,j,t:longint; 53begin 54if f=n+1then 55begin 56t:=0; 57for j:=1to n do 58t:=t+b[j]; 59if t<>x then exit; 60qq; 61exit; 62end; 63for i:=0to1do 64begin 65b[f]:=i; 66pp(f+1); 67end; 68end; 69begin 70assign(input,'submatrix.in'); 71assign(output,'submatrix.out'); 72reset(input); 73rewrite(output); 74read(n,m,x,y); 75for i:=1to n do 76for j:=1to m do 77read(a[i,j]); 78min:=maxlongint; 79pp(1); 80writeln(min); 81close(input); 82close(output); 83end. 【数据分析】 这道题目的平均分是17.9分,应该算是一道比较难的题目了。 本题100分共有11人。 50、55分,时间复杂度为O(2*2n)的深搜即可获得。 65分,在时间复杂度为O(2*2n)的深搜的程度上略加一些记忆话剪枝即可。 【总结】 深度优先搜索、DP。