1、noip普及组复赛解题报告N O I P 2 0 1 5 普 及 组 解 题 报 告南京师范大学附属中学树人学校 CT1.金币 (coin.cpp/c/pas)【问题描述】 国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金 币;之后两天 (第二天和第三天),每天收到两枚金币; 之后三天(第 四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十 天),每天收到四枚金币 ;这种工资发放模式会一直这样延续下 去:当连续N天每天收到N枚金币后,骑士会在之后的连续N+1天里, 每天收到 N+1 枚金币。请计算在前 K 天里,骑士一共获得了多少金币。【输入格式】输入文件名为 coin.
2、in 。输入文件只有1行,包含一个正整数K,表示发放金币的天数。【输出格式】输出文件名为 coin.out 。输出文件只有 1 行,包含一个正整数,即骑士收到的金币数。【数据说明】对于 100%勺数据,1 K 10,000。【思路】模拟时空复杂度】O(k) ,O(1)2、扫雷游戏( mine.cpp/c/pas )【问题描述】扫雷游戏是一款十分经典的单机小游戏。 在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷 (称之为非地雷格) 玩家翻开一个非地雷格时, 该格将会出现一个数字提示周围格子 中有多少个是地雷格。游戏的目标是在不翻出任何地雷格的条件下, 找出所有的非地雷格。
3、现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围 的地雷格数。注:一个格子的周围格子包括其上、 下、左、右、左上、右上、左下、 右下八个方向上与之直接相邻的格子。【输入格式】输入文件名为 mine.in 。输入文件第一行是用一个空格隔开的两个整数 n和m分别表示雷区的 行数和列数。接下来n行,每行m个字符,描述了雷区中的地雷分布情况。字符* 表示相应格子是地雷格,字符 ?表示相应格子是非地雷格。相邻 字符之间无分隔符。【输出格式】输出文件名为 mine.out 。输出文件包含n行,每行m个字符,描述整个雷区。用* 表示地雷 格,用周围的地雷个数表示非地雷格。相邻字符之间无分隔符。
4、【数据说明】对于 100%勺数据,1 nW 100, 1 me 100【思路】模拟【技巧】 可将数组多开一圈,省去边界条件勺判断。【时空复杂度】O(mn), O(mn)3.求和 (sum.cpp/c/pas)【问题描述】一条狭长的纸带被均匀划分出了 n个格子,格子编号从1到n。每个 格子上都染了一种颜色color i (用1,m当中的一个整数表示),并 且写了一个数字 numberi。定义一种特殊的三元组: (x,y,z) ,其中 x, y, z 都代表纸带上格子 的编号,这里的三元组要求满足以下两个条件:1.x,y,z 都是整数,xyz,y?x二z?y2.color x=color z满足上
5、述条件的三元组的分数规定为 (x+z)*(number x+numberz) 。整个 纸带的分数规定为所有满足条件的三元组的分数的和。 这个分数可能 会很大,你只要输出整个纸带的分数除以 10,007 所得的余数即可。 【输入格式】输入文件名为 sum.in 。第一行是用一个空格隔开的两个正整数 n 和 m, n 代表纸带上格子的个数,m代表纸带上颜色的种类数。第二行有n个用空格隔开的正整数,第i个数字number代表纸带上 编号为 i 的格子上面写的数字。第三行有m个用空格隔开的正整数,第i个数字color i代表纸带上编 号为 i 的格子染的颜色。【输出格式】输出文件名为 sum.out。
6、共一行,一个整数,表示所求的纸带分数除以 10,007 所得的余数。数据说明】对于第5组至第6组数据,1W nW 100000,1 W mW 100000,且不存在出现次数超过 20 的颜色;1WnW100000,1W mW100000,1Wcolor iWm,1 W numberiW100000。【思路】 先分析一下,我们的任务是什么。题目的要求是求分数和,我们就得 把所有符合条件的三元组“找”出来。至少需要枚举三元组(x,y,z)中的一个元素,这里枚举的是z (当然x 也可以,不过不要选y,因为y对分数没什么用)。1、 因为xyz,所以只需向前枚举x,y2、 因为 y-x=z-y ,所以
7、x+z=2y , x、 z 同奇偶,且分数与 y 无关,只 需枚举 z 和 x。3、 因为colour x=colour z,所以只需枚举z之前同奇偶且同色的X。 这样的话时间复杂度是 O(n2),能得40分。如何快速枚举x呢? 其实不是快速枚举X,是快速枚举分数和。观察三元组分数: (x+z) (numberx+ number)显然我们不方便处理多项式乘法,那就把它拆开(事实上很多人到这步都放弃了,其实试一试立刻就明白了) =x numbeix+x numbeiz+z numbeix+z numberz那么对于z的所有合法决策x1,x2, ,xk根据乘法分配率,分数二艺(xi*number力
8、)+艺(xi)*number z+艺 (nu mberxi)*z+ 艺(z* nu mberz)(1v=iv二k)由于z是枚举的,所以只需快速得到艺(xnumbeix),艺x,艺number 和k (注意最后一项被算了 k次,要乘k)这样我们就可以开 4 个累加器, 分别记录这四个量。 而对于不同奇偶性、不同颜色的z有不同的决策x,所以要开一个s2m4的累加 器。【时空复杂度】 0(n), 0(n+m)【注意】题目数据较大, 每次计算一定要模 10007,否则很容易出错 【样例程序】#include constintmaxn=100000;constintmaxm=100000;constin
9、tp=10007;intn,m,ans;intnumbermaxn+1,colourmaxn+1;ints2maxm+14;voidinit()freopen(sum.in,r,stdin);freopen(sum.out,w,stdout);scanf(%d%d,&n,&m); for(inti=1;i=n;i+) scanf(%d,&numberi);for(inti=1;i=n;i+)scanf(%d,&colouri);voidsolve()for(inti=1;i=n;i+)intz=i%p,numz=numberi%p,c=colouri,t=i%2;intcount=stc0%=
10、p,x=stc1%=p, numx=stc2%=p,x_numx=stc3%=p; ans=(ans+(count*z)%p*numz)%p)%p; ans=(ans+x_numx)%p; ans=(ans+x*numz)%p; ans=(ans+z*numx)%p;stc0+;stc1+=z;stc2+=numz;stc3+=z*numz;voidoutput()printf(%dn,ans);fclose(stdin);fclose(stdout);intmain()init();solve();output();return0;4.推销员 (salesman.cpp/c/pas)【问题描
11、述】 阿明是一名推销员, 他奉命到螺丝街推销他们公司的产品。 螺丝街是一 条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有N家住户,第i家住户到入口的距离为 S米。由于同一 栋房子里可以有多家住户, 所以可能有多家住户与入口的距离相等。 阿 明会从入口进入,依次向螺丝街的X家住户推销产品,然后再原路走出 去。阿明每走 1 米就会积累 1 点疲劳值,向第 i 家住户推销产品会积累 Ai 点疲劳值。阿明是工作狂,他想知道,对于不同的 X,在不走多余的路 的前提下,他最多可以积累多少点疲劳值。【输入格式】输入文件名为 salesman.in 。第一行有一个正整数N,表示螺丝
12、街住户的数量。接下来的一行有N个正整数,其中第i个整数S表示第i家住户到入 口的距离。数据保证SWS2W-VS n108。接下来的一行有N个正整数,其中第i个整数A表示向第i户住户推 销产品会积累的疲劳值。数据保证 Ai103。【输出格式】输出文件名为 salesman.out 。输出N行,每行一个正整数,第i行整数表示当X=i时,阿明最多积累 的疲劳值。数据说明】 对于20%勺数据,1 N 20;对于40%勺数据,1 N 100;对于60%勺数据,1 N 1000;对于 100%勺数据,1 N 100000。【思路】题目要求每一个X的情况,显然不能每个X专门推一遍,要充分利用已 知的X的情况
13、,那么很可能会是 DP定义 fi 为 X=i 时勺最大疲劳值。关键是怎么建立状态转移方程呢?考试时观察了两组样例数据, 直觉告 诉我 fi+1 勺决策应该会包含 fi 勺决策(此处勺决策指住户下标) 。 事实上也确实如此。证明:设fi的决策为k,k2,ki(k1k2kj , fi+1的决策将fi决策中的ks换成j并增加了一个决策ki+1, fi+1的决策k中最大的为 maxk。fi=2*sk i+ 艺 akt(1=t=i)fi+1=2*smaxk+ 艺 akt(1=t=s-1)+ 艺ak t(s+1=t=i)+aj+ak i+1 v 2*smaxk+ 艺 akt(1=t=s-1)+ 艺 akt
14、(s+1=t=i)+aj 是 X=i 时 的一种决策的疲劳值(即决策为 k1,k2,ks-1,ks+1,ki,kj时)2*smaxk+ 艺 akt(1=t=s-1)+ 艺 akt(s+1=t=i)+aj=fi2*smaxk+ 艺 akt(1=t=s-1)+ 艺 akt(s+1=t=i)+aj+ak i+1 =fi+ak i+1(即决策为k1,k2,ks,ki,ki+1时的疲劳值)若小于,说明保留 ks 更优;若等于,对于两个目前疲劳值相等的决策序列 k,maxk 越小越好(就是说目前走的路程越短越好) ,因为在 maxk 左边的决策 l 只能增加 al 的 疲 劳 值 , 而 对 于 maxk
15、 右 边 的 决 策 r 则 可 以 增 加 2*(sr-smaxk)+ar ,对于左边,maxk没有影响,而对于右边,maxk 越小,后面的 f 增加疲劳值的空间越大。根据以上原则,虽然决策 ki,k2,ks,ki与决策ki,k2,ks-i ,ks+i, ki,k j疲劳值相等,但fi选择了前者,说明前者更优。综上,无论小于或等于,决策ki,k2, ,k s, ,k i都比决策ki,k2, ks-1 ,k s+1, ki ,k j 更优。 fi+1的最优决策一定包含了 fi的最优决策,证毕.也就是说,对于 fi ,只需在 fi-1 的基础上加一个最优决策就可以了。易得出状态转移方程:fi=f
16、i-1+maxmaxak|1=kmaxi,max2*(sk-smaxi)+ak|maxik=n其中 k 表示待选决策(已经选过的不能再选) , maxi 表示 fi-1 的决策序列 中最大的一个决策。解释一下:因为maxi左边的决策k不会增加距离,只增加推销的疲劳值ak; 而 maxi 右边的决策 k 不仅会增加疲劳值, 还会使距离从 smaxi 增加到 sk , 来回还要x 2,也就是增加了 2*(sk-smaxi)+ak枚举k的话时间复杂度是O(n2)的,只能得60分。做到这里,优化已经很明显了。对于 maxi 的左边,我们需要动态求区间 ak最值,无论是堆、线段树、优先级队列、集合,时间
17、复杂度都是 log 2n 级别的;而对于 maxi 右边,由于所有决策疲劳值都要减去 smaxi ,所以我们只要求区 间 2*sk+ak 的最值,而且可用决策必然是不断减少的,可以预处理排个递 减序,每次找第一个合法决策。当然,左边的方法对于右边同样适用。同时, 如果选择了右边的决策k,还需要将maxi到k之间的决策加入到左边的待选决 策中。(因为它们的身份已经从右边的点变成了左边的点) 。 由于每个决策最多从右边出一次,从左边进一次、出一次,均摊时间复杂度是 O(nlog 2n) , 能得 100 分。(可惜考试时多写了一个“ ”,编译错误, 400变成了 300) 【时空复杂度】O(nlo
18、g 2n), O(n) (取决于使用的数据结构)【样例程序】heap:#include#include usingnamespacestd;constintmaxn=100000;intn;intsmaxn+1,amaxn+1,fmaxn+1; intleftmaxn+1,rightmaxn+1,l,r=1,maxi;boolcmpl(constinti,constintj)if(ai!=aj)returnaij;boolcmpr(constinti,constintj)if(2*si+ai!=2*sj+aj)return2*si+ai2*sj+aj;elsereturnij;voidinit
19、()freopen(salesman.in,r,stdin);freopen(salesman.out,w,stdout);scanf(%d,&n);for(inti=1;i=n;i+)scanf(%d,&si);for(inti=1;i=n;i+)scanf(%d,&ai);righti=i;sort(right+1,right+n+1,cmpr);voidchoose_left(constinti)fi=fi-1+aleft1;pop_heap(left+1,left+l-+1,cmpl);voidchoose_right(constinti)fi=fi-1+2*(srightr-smax
20、i)+arightr;for(inti=maxi+1;irightr;i+)left+l=i;push_heap(left+1,left+l+1,cmpl);maxi=rightr+;while(r=n&rightr=maxi)r+;voidsolve()for(inti=1;in)choose_left(i);elseif(aleftl=2*(srightr-smaxi)+arightr)choose_left(i);elsechoose_right(i);voidoutput()for(inti=1;i=n;i+)printf(%dn,fi);fclose(stdin);fclose(st
21、dout);intmain()init();solve();output();return0;set:#include#includeusingnamespacestd;constintmaxn=100000;intn;intsmaxn+1,amaxn+1,fmaxn+1; intleftmaxn+1,rightmaxn+1,l,r=1,maxi; boolcmpl(constinti,constintj)if(ai!=aj)returnaij;boolcmpr(constinti,constintj)if(2*si+ai!=2*sj+aj) return2*si+ai2*sj+aj;else
22、returnij;voidinit()freopen(salesman.in,r,stdin);freopen(salesman.out,w,stdout);scanf(%d,&n);for(inti=1;i=n;i+)scanf(%d,&si);for(inti=1;i=n;i+)scanf(%d,&ai);righti=i;sort(right+1,right+n+1,cmpr); voidchoose_left(constinti)fi=fi-1+aleft1;pop_heap(left+1,left+l-+1,cmpl); voidchoose_right(constinti)fi=f
23、i-1+2*(srightr-smaxi)+arightr;for(inti=maxi+1;irightr;i+)left+l=i; push_heap(left+1,left+l+1,cmpl);maxi=rightr+;while(r=n&rightr=maxi)r+;voidsolve()for(inti=1;in) choose_left(i);elseif(aleftl=2*(srightr-smaxi)+arightr) choose_left(i);elsechoose_right(i);voidoutput()for(inti=1;i=n;i+)printf(%dn,fi);f
24、close(stdin);fclose(stdout);intmain()init();solve();output();return0;priority_queue:#include#include constintmaxn=100000;usingnamespacestd;intn;intsmaxn+1,amaxn+1,fmaxn+1,maxi; classcmplpublic:booloperator()(constinti,constintj)returnaiaj;classcmprpublic:booloperator()(constinti,constintj)if(2*si+ai
25、!=2*sj+aj) return2*si+aij;priority_queueint,vector,cmplleft; priority_queueint,vector,cmprright; voidinit()freopen(salesman.in,r,stdin); freopen(salesman.out,w,stdout); scanf(%d,&n);for(inti=1;i=n;i+)scanf(%d,&si);for(inti=1;i=n;i+)scanf(%d,&ai);right.push(i);voidchoose_left(constinti)fi=fi-1+aleft.
26、top();left.pop(); voidchoose_right(constinti)intr=right.top();fi=fi-1+2*(sr-smaxi)+ar;for(inti=maxi+1;ir;i+)left.push(i);maxi=r;while(!right.empty()&right.top()=maxi)right.pop();voidsolve()for(inti=1;i=2*(sr-smaxi)+ar)choose_left(i);elsechoose_right(i);voidoutput()for(inti=1;i=n;i+) printf(%dn,fi);fclose(stdin);fclose(stdout);intmain()init();solve();output();return0;鉴于set和priority_queue 属于C+STL实现起来可能较宏观、容易,但效率会略比 heap 低一些。自测时 heap 约 0.43s ,set 约 0.7s ,priority_queue 约 0.94s 。
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1