noip提高组复赛解题报告.docx
《noip提高组复赛解题报告.docx》由会员分享,可在线阅读,更多相关《noip提高组复赛解题报告.docx(9页珍藏版)》请在冰豆网上搜索。
noip提高组复赛解题报告
noip2017提高组复赛解题报告
定期推送帐号信息学新闻,竞赛自主招生,信息学专业知识,信息学疑难解答,融科教育信息学竞赛培训等诸多优质内容的微信平台,欢迎分享文章给你的朋友或者朋友圈!
以下解题思路及代码未经官方评测,仅供参考,复赛成绩以官方(CCF)评测结果为准。
Day1
1.小凯的疑惑(math.cpp/c/pas)【问题描述】小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。
每种金币小凯都有无数个。
在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。
现在小凯想知道在无法准确支付的物品中,最贵的价值是多少金币?
注意:
输入数据保证存在小凯无法准确支付的商品。
【输入格式】输入文件名为math.in。
输入数据仅一行,包含两个正整数a和b,它们之间用一个空格隔开,表示小凯手中金币的面值。
【输出格式】输出文件名为math.out。
输出文件仅一行,一个正整数N,表示不找零的情况下,小凯用手中的金币不能准确支付的最贵的物品的价值。
【输入输出样例1】math.in37math.out11【数据规模与约定】对于30%的数据:
1≤a,b≤50。
对于60%的数据:
1≤a,b≤10,000。
对于100%的数据:
1≤a,b≤1,000,000,000。
数学太差只找规律吧。
设:
其中一个数为2则:
2、3=>1;2、5=>3;2、7=>5;2、11=>9得:
2、n=>n-2设:
其中一个数为3则:
3、5=>7;3、7=>11;3、11=>19;3、13=>23得:
3、n=>2n-3设:
其中一个数为5则:
5、7=>23;5、11=>39;5、13=>47;5、17=>63得:
5、n=>4n-5所以:
m、n=>(m-1)n-m#includeusingnamespacestd;intmain(){longlonga,m,n;scanf('%lld%lld',&m,&n);a=(m-1)*n-m;printf('%lld',a);return0;}2.时间复杂度(complexity.cpp/c/pas)【问题描述】小明正在学习一种新的编程语言A++,刚学会循环语句的他激动地写了好多程序并给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!
下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++语言的循环结构如下:
其中“Fixy”表示新建变量(i变量i不可与未被销毁的变量重名)并初始化为x,然后判断i和y的大小关系,若i小于等于y则进入循环,否则不进入。
每次循环结束后i都会被修改成i+1,一旦i大于y终止循环。
x和y可以是正整数(x和y的大小关系不定)或变量n。
n是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于100。
“E”表示循环体结束。
循环体结束时,这个循环体新建的变量也被销毁。
注:
本题中为了书写方便,在描述复杂度时,使用大写英文字母“O”表示通常意义下“Θ”的概念。
【输入格式】输入文件名为complexity.in。
输入文件第一行一个正整数t,表示有t(t≤10)个程序需要计算时间复杂度。
每个程序我们只需抽取其中“Fixy”和“E”即可计算时间复杂度。
注意:
循环结构允许嵌套。
接下来每个程序的第一行包含一个正整数L和一个字符串,L代表程序行数,字符串表示这个程序的复杂度,“O
(1)”表示常数复杂度,“O(n^w)”表示复杂度为n^w,其中w是一个小于100的正整数(输入中不包含引号),输入保证复杂度只有O
(1)和O(n^w)两种类型。
接下来L行代表程序中循环结构中的“Fixy”或者“E”。
程序行若以“F”开头,表示进入一个循环,之后有空格分离的三个字符(串)ixy,其中i是一个小写字母(保证不为“n”),表示新建的变量名,x和y可能是正整数或n,已知若为正整数则一定小于100。
程序行若以“E”开头,则表示循环体结束。
【输出格式】输出文件名为complexity.out。
输出文件共t行,对应输入的t个程序,每行输出“Yes”或“No”或者“ERR”(输出中不包含引号),若程序实际复杂度与输入给出的复杂度一致则输出“Yes”,不一致则输出“No”,若程序有语法错误(其中语法错误只有:
F和E不匹配;新建的变量与已经存在但未被销毁的变量重复两种情况),则输出“ERR”。
注意:
即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出“ERR”。
【输入输出样例1】complexity.in82O
(1)Fi11E2O(n^1)Fx1nE1O
(1)Fx1n4O(n^2)Fx5nFy10nEE4O(n^2)Fx9nEFy2nE4O(n^1)Fx9nFyn4EE4O
(1)Fyn4Fx9nEE4O(n^2)Fx1nFx110EEcomplexity.outYesYesERRYesNoYesYesERR【数据规模与约定】对于30%的数据:
不存在语法错误,数据保证小明给出的每个程序的前L/2行一定为以F开头的语句,第L/2+1行至第L行一定为以E开头的语句,L,若x、y均为整数,x一定小于y,且只有y有可能为n。
对于50%的数据:
不存在语法错误,L,且若x、y均为整数,x一定小于y,且只有y有可能为n。
对于70%的数据:
不存在语法错误,L。
对于100%的数据:
L。
用STLstack模拟,对于用代码量堆出来的OIer来说,这就是信心倍增的大力模拟题。
代码就不上了。
3.逛公园(park.cpp/c/pas)【问题描述】策策同学特别喜欢逛公园。
公园可以看成一张N个点M条边构成的有向图,且没有自环和重边。
其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从1号点进去,从N号点出来。
策策喜欢新鲜的事物,他不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,他不希望每天在逛公园这件事上花费太多的时间。
如果1号点到N号点的最短路长为d,那么策策只会喜欢长度不超过d+K的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮他吗?
为避免输出过大,答案对P取模。
如果有无穷多条合法的路线,请输出?
1。
【输入格式】输入文件名为park.in。
第一行包含一个整数T,代表数据组数。
接下来T组数据,对于每组数据:
第一行包含四个整数N,M,K,P,每两个整数之间用一个空格隔开。
接下来M行,每行三个整数ai,bi,ci,代表编号为ai,bi的点之间有一条权值为ci的有向边,每两个整数之间用一个空格隔开。
【输出格式】输出文件名为park.out。
输出文件包含T行,每行一个整数代表答案。
【输入输出样例1】park.in25721012124045223234135215322010120210park.out3-1【数据规模与约定】对于不同的测试点,我们约定各种参数的规模不会超过如下:
对于100%的数据,1≤P≤109,1≤ai,bi≤?
?
0≤ci≤1000。
数据保证:
至少存在一条合法的路线。
最短路+拓扑排序+DP先跑最短路。
发现K只有50,所以一定是要从K入手。
所以考虑DP,令f[i][j]表示走到i,多走的长度是j的方案数。
(多走指的是比最短路多的部分的长度)。
但是发现这个DP方程是存在环的,因为最短路径图上的边以及零边都是可以同行转移的。
将最短路径图上的边以及零边都拿出来跑拓扑排序,然后让这些边在转移时必须沿着拓扑序转移即可。
特别地,如果一个零环位于一条从1到n长度的路径上,则输出-1即可。
#include#include#include#include#include#include#definemp(A,B)make_pair(A,B)usingnamespacestd;constintmaxn=100010;constintmaxm=200010;intn,m,cnt,K,P,tot,ans;intto[maxm],next[maxm],val[maxm],head[maxn],dis[maxn],vis[maxn],d[maxn],q[maxnintto2[maxm],next2[maxm],val2[maxm],head2[maxn],dis2[maxn];priority_queue>pq;inlineintrd(){intret=0;chargc=getchar();while(gc'9')gc=getchar();while(gc>='0'&&gcreturnret;}inlinevoidadd(inta,intb,intc){to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt;to2[cnt]=a,val2[cnt]=c,next2[cnt]=head2[b],head2[b]=cnt++;}inlinevoidupd(int&x,inty){x=(x+y)%P;}voidwork(){n=rd(),m=rd(),K=rd(),P=rd();inti,j,k,a,b,c,u;memset(head,-1,sizeof(head)),memset(head2,-1,sizeof(head2)),cnt=tot=ans=0;for(i=1;i//1memset(vis,0,sizeof(vis)),memset(dis,0x3f,sizeof(dis)),memset(d,0,sizeof(d));pq.push(mp(0,1)),dis[1]=0;while(!
pq.empty()){u=pq.top().second,pq.pop();if(vis[u])continue;vis[u]=1;for(i=head[u];i!
=-1;i=next[i])if(dis[to[i]]>dis[u]+val[i])dis[to[i]]=dis[u]+val[i],pq.push(mp(-dis[to[i]],to[i]));}//2memset(dis2,0x3f,sizeof(dis2)),memset(vis,0,sizeof(vis));pq.push(mp(0,n)),dis2[n]=0;while(!
pq.empty()){u=pq.top().second,pq.pop();if(vis[u])continue;vis[u]=1;for(i=head2[u];i!
=-1;i=next2[i])if(dis2[to2[i]]>dis2[u]+val2[i])dis2[to2[i]]=dis2[u]+val2[i],pq.push(mp(-dis2[to2[i]],to2[i]));}//3for(i=1;ifor(i=1;ifor(j=1;j{u=q[j];for(i=head[u];i!
=-1;i=next[i])if(dis[u]+val[i]==dis[to[i]]){d[to[i]]--;if(!
d[to[i]])q[++tot]=to[i];}}for(i=1;i{printf('-1\n');return;}//DPmemset(f,0,sizeof(f));f[0][1]=1;for(k=0;k{for(i=1;i{upd(f[k][to[j]],f[k][u]);}for(i=1;i{upd(f[k+dis[i]+val[j]-dis[to[j]]][to[j]],f[k][i]);}}for(i=0;iprintf('%d\n',ans);}intmain(){intT=rd();while(T--)work();return0;}
Day21.奶酪(cheese.cpp/c/pas)【问题描述】现有一块大奶酪,它的高度为h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。
我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为z=0,奶酪的上表面为z=h。
现在,奶酪的下表面有一只小老鼠Jerry,它知道奶酪中所有空洞的球心所在的坐标。
如果两个空洞相切或是相交,则Jerry可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry则可以从空洞跑到奶酪上表面。
位于奶酪下表面的Jerry想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?
空间内两点P(x,y,z)、P(x,y,z)的距离公式如下:
【输入格式】输入文件名为cheese.in。
每个输入文件包含多组数据。
输入文件的第一行,包含一个正整数T,代表该输入文件中所含的数据组数。
接下来是T组数据,每组数据的格式如下:
第一行包含三个正整数n,h和r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。
接下来的n行,每行包含三个整数x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为(x,y,z)。
【输出格式】输出文件名为cheese.out。
输出文件包含T行,分别对应T组数据的答案,如果在第i组数据中,Jerry能从下表面跑到上表面,则输出“Yes”,如果不能,则输出“No”(均不包含引号)。
【输入输出样例1】cheese.in3241001003251001004252002204cheese.outYesNoYes【数据规模与约定】对于20%的数据,n=1,1≤h,r≤10,000,坐标的绝对值不超过10,000。
对于40%的数据,1≤n≤8,1≤h,r≤10,000,坐标的绝对值不超过10,000。
对于80%的数据,1≤n≤1,000,1≤h,r≤10,000,坐标的绝对值不超过10,000。
对于100%的数据,1≤n≤1,000,1≤h,r≤1,000,000,000,T≤20,坐标的绝对值不超过1,000,000,000。
真正的T1,并查集+高二数学常识。
用并查集做,将位置相距小于等于2倍半径的归为一个集合。
注意:
Z位置+半径>=H的,与1001归为一个集合。
Z位置-半径的,与0归为一个集合。
最后看0和1001是否在一个集合中。
#include#include#include#include#include#include#include#includeusingnamespacestd;intt,n,h,r,x,y,z,f,fa[1002],t1,t2;structsb{intx;inty;intz;}a[1001];intfind(intk){if(fa[k]!
=k)fa[k]=find(fa[k]);returnfa[k];}intmain(){cin>>t;for(inti=1;i{cin>>n>>h>>r;for(inti=1;ifa[i]=i;fa[0]=0;fa[1001]=1001;for(intj=1;j{cin>>a[j].x>>a[j].y>>a[j].z;if(a[j].z+r>=h){t1=find(j);t2=find(1001);fa[t1]=t2;}if(a[j].z-r{t1=find(j);t2=find(0);fa[t1]=t2;}for(intw=1;w{if(sqrt((a[j].x-a[w].x)*(a[j].x-a[w].x)+(a[j].y-a[w].y)*(a[j].y-a[w].y)+(a[j].z-a[w].z)*(a[j].z-a[w].z)){t1=find(j);t2=find(w);fa[t1]=t2;}}}t1=find(0);t2=find(1001);if(t1==t2)coutelsecout}return0;}2.宝藏(treasure.cpp/c/pas)【问题描述】参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了n个深埋在地下的宝藏屋,也给出了这n个宝藏屋之间可供开发的m条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。
但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。
已经开凿出的道路可以任意通行不消耗代价。
每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。
另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。
新开发一条道路的代价是:
这条道路的长度×从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。
请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。
【输入格式】输入文件名为treasure.in。
第一行两个用空格分离的正整数n和m,代表宝藏屋的个数和道路数。
接下来m行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为1~n),和这条道路的长度v。
【输出格式】输出文件名为treasure.out。
输出共一行,一个正整数,表示最小的总代价。
【输入输出样例1】treasure.in45121133141234341treasure.out4【样例输入输出2】treasure.in45121133141234342treasure.out5【数据规模与约定】对于20%的数据:
保证输入是一棵树,1≤n≤8,v≤5000且所有的v都相等。
对于40%的数据:
1≤n≤8,0≤m≤1000,v≤5000且所有的v都相等。
对于70%的数据:
1≤n≤8,0≤m≤1000,v≤5000对于100%的数据:
1≤n≤12,0≤m≤1000,v≤500000
状压DP一看n,明显是状压DP的数据范围。
于是令f[i][S]表示当前与根连通的点的状态为S,并且最深的点的深度为i的最小代价。
转移时,我们枚举所有不在S中的点,处理出每个点与S中的某个点连通所需要的最小代价。
然后枚举这些点构成的所有集合S',用S'中所有点的代价+f[i][S]去更新f[i+1][S|S']即可。
时间复杂度:
O(n3n)。
#include#include#includeusingnamespacestd;typedeflonglongll;intn,m,tot,ans;intmap[110][110],dis[110][110],Log[4100];intf[15][4100],g[4100],ref[4100],v[15],p[15];inlineintmin(constint&a,constint&b){returna}intmain(){scanf('%d%d',&n,&m);registerinti,j,a,b,c,x;for(i=0;ifor(i=1;i{scanf('%d%d%d',&a,&b,&c),a--,b--;map[a][b]=map[b][a]=min(map[a][b],c);}for(i=0;imemset(f,0x3f,sizeof(f));for(i=0;ifor(i=0;i{tot=0;for(a=0;a{v[tot]=60000000,p[tot]=1for(j=x;j;j-=j&-j){b=Log[j&-j];v[tot]=min(v[tot],map[a][b]*(i+1));}tot++;}for(j=1;j{g[j]=g[j-(j&-j)]+v[Log[j&-j]];ref[j]=ref[j-(j&-j)]|p[Log[j&-j]];f[i+1][x|ref[j]]=min(f[i+1][x|ref[j]],f[i][x]+g[j]);}}ans=1for(i=0;iprintf('%d',ans);return0;}3.列队(phalanx.cpp/c/pas)【问题描述】Sylvia是一个热爱学习的女孩子。
前段时间,Sylvia参加了学校的军训。
众所周知,军训的时候需要站方阵。
Sylvia所在的方阵中有n×m名学生,方阵的行数为n,列数为m。
为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从1到n×m编上了号码(参见后面的样例)。
即:
初始时,第i行第j列的学生的编号是(i?
1)×m+j。
然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。
在一天中,一共发生了q件这样的离队事件。
每一次离队事件可以用数对(x,y)(1≤x≤n,1≤y≤m)描述,表示第x行第y列的学生离队。
在有学生离队后,队伍中出现了一个空位。
为了队伍的整齐,教官会依次下达这样的两条指令:
1.向左看齐。
这时第一列保持不动,所有学生向左填补空缺。
不难发现在这条指令之后,空位在第x行第m列。
2.向前看齐。
这时第一行保持不动,所有学生向前填补空缺。
不难发现在这条指令之后,空位在第n行第m列。
教官规定不能有两个或更多学生同时离队。
即在前一个离队的学生归队之后,下一个学生才能离队。
因此在每一个离队的学生要归队时,队伍中有且仅有第n行第m列一个空位,这时这个学生会自然地填补到这个位置。
因为站方阵真的很无聊,所以Sylvia想要计算每一次离队事件中,离队的同学的编号是多少。
注意:
每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。
【输入格式】输入文件名为phalanx.in。
输入共q+1行。
第1行包含3个用空格分隔的正整数n,m,q,表示方阵大小是n行m列,一共发生了q次事件。
接下来q行按照事件发生顺序描述了q件事件。
每一行是两个整数x,y,用一个空格分隔,表示这个离队事件中离队的学生当时排在第x行第y列。
【输出格式】输出文件名为phalanx.out。
按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。
【输入输出样例1】phalanx.in223112212phalanx.out114【数据规模与约定】数据保证每一个事件满足1≤x≤n,1≤y≤m。
离线+平衡树由于操作是可逆的,所以将所有操作反过来做,将人的移动改成询问的反向移动,那么只需要维护这些询问点的移动即可,不难想到用数据结构。
将操作顺序反过来,那么一次操作(x,y)可以看成:
1.(n,m)的学生出队;2.第n列中,第x行及以下的所有学生