NOIP2017普及组解题报告
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NOIP2017普及组解题报告相关的知识,希望对你有一定的参考价值。
NOIP2017普及组解题报告
T1:成绩(score)
这题作为普及组的第一题,没有任何难度,以至于我的同学们都开玩笑说,这题考的就是freopen的用法。但是成绩公布了之后,很多人都是30分、60分,原因是Linux下的浮点误差。虽然CCF后面说会浮点误差不在考察范围内,会重新评测,但是这也是一个教训,以后我对浮点数会更加小心。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 5 int main() { 6 freopen("score.in","r",stdin); 7 freopen("score.out","w",stdout); 8 int a,b,c; 9 cin>>a>>b>>c; 10 int s; 11 s=a/10*2+b/10*3+c/10*5; 12 cout<<s<<endl; 13 fclose (stdin); 14 fclose (stdout); 15 return 0; 16 }
T2:图书管理员(librarian)
在T1浮点误差的陷阱下,这题莫名成了这次普及组最水的一题。唯一需要注意的地方就是这比其他三个题目都长,而且没见过的文件名。我直接用数字来处理。题目里给了需求码的长度,所以简单很多,不然还要自己写个函数判断位数。
另外,有个小技巧:求一个正整数的最后x位,只要把这个数对10x取模即可。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn=1000; 7 int n,m; 8 int a[maxn+1]; 9 int cifang[9]; 10 11 void init() { 12 cin>>n>>m; 13 for (int i=1; i<=n; i++) 14 scanf("%d",&a[i]); 15 sort(a+1,a+1+n); 16 cifang[0]=1; 17 for (int i=1; i<=8; i++) 18 cifang[i]=cifang[i-1]*10; 19 } 20 21 int main() { 22 freopen("librarian.in","r",stdin); 23 freopen("librarian.out","w",stdout); 24 init(); 25 for (int i=1; i<=m; i++) { 26 int changdu,x; 27 bool o=false; 28 scanf("%d %d",&changdu,&x); 29 for (int j=1; j<=n; j++) 30 if (a[j]%cifang[changdu]==x) { 31 cout<<a[j]<<endl; 32 o=true; 33 break; 34 } 35 if (o==false) 36 cout<<"-1"<<endl; 37 } 38 fclose (stdin); 39 fclose (stdout); 40 return 0; 41 }
T3:棋盘(chess)
本题我用的是暴力dfs。对于每一个点,枚举向上、下、左、右四个方向移动的每一种情况,一直搜索下去,直到无路可走或者到达终点。那应该怎么决定应该把没有颜色的格子变为什么颜色呢?让我们列举每一种情况:① 当连接的两个格子颜色相同时,最好的方法是把中间的格子变为与这两个格子相同的颜色; ② 当连接两个格子颜色不同时,那随意变颜色都可以。因为我们不知道将要搜索到的格子的下一个颜色是什么,所以我们需要找出一个共同具有的特点。稍加思考,便可得出,只要把这个格子变为前一个格子的颜色,就符合最优条件了。
在考场上,我稍作判断,深搜的时间复杂度为O(节点数×转移的数量)。本题中节点数为m2,每一次dfs往四个方向走,所以转移数是4。对于最大的m=100,时间显然还绰绰有余。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 const int maxm=100; 7 int m,n; 8 int a[maxm+1][maxm+1]; 9 int ans[maxm+1][maxm+1]; 10 11 void kuaidu(int &p) { 12 char c; 13 p=0; 14 do c=getchar(); 15 while (c<‘0‘||c>‘9‘); 16 do p=p*10+c-‘0‘, c=getchar(); 17 while (c>=‘0‘&&c<=‘9‘); 18 } 19 20 void init() { 21 cin>>m>>n; 22 memset(a,-1,sizeof(a)); 23 memset(ans,0x7F,sizeof(ans)); 24 for (int i=1; i<=n; i++) { 25 int x,y,z; 26 kuaidu(x); 27 kuaidu(y); 28 kuaidu(z); 29 a[x][y]=z; 30 } 31 ans[1][1]=0; 32 } 33 34 void dfs(int x, int y, int col) { 35 if (x==m&&y==m) 36 return; 37 //其实还有一种向量数组的写法,但考场上为了保险,宁愿写长一点 38 if (x<m) { 39 int s=0; 40 if (a[x+1][y]!=col) 41 s++; 42 if (a[x+1][y]==-1) 43 s++; 44 if ((a[x+1][y]!=-1||a[x][y]!=-1)&&ans[x+1][y]>ans[x][y]+s) { 45 ans[x+1][y]=ans[x][y]+s; 46 dfs(x+1,y,a[x+1][y]==-1?a[x][y]:a[x+1][y]); 47 } 48 } 49 if (y<m) { 50 int s=0; 51 if (a[x][y+1]!=col) 52 s++; 53 if (a[x][y+1]==-1) 54 s++; 55 if ((a[x][y+1]!=-1||a[x][y]!=-1)&&ans[x][y+1]>ans[x][y]+s) { 56 ans[x][y+1]=ans[x][y]+s; 57 dfs(x,y+1,a[x][y+1]==-1?a[x][y]:a[x][y+1]); 58 } 59 } 60 if (x>1) { 61 int s=0; 62 if (a[x-1][y]!=col) 63 s++; 64 if (a[x-1][y]==-1) 65 s++; 66 if ((a[x-1][y]!=-1||a[x][y]!=-1)&&ans[x-1][y]>ans[x][y]+s) { 67 ans[x-1][y]=ans[x][y]+s; 68 dfs(x-1,y,a[x-1][y]==-1?a[x][y]:a[x-1][y]); 69 } 70 } 71 if (y>1) { 72 int s=0; 73 if (a[x][y-1]!=col) 74 s++; 75 if (a[x][y-1]==-1) 76 s++; 77 if ((a[x][y-1]!=-1||a[x][y]!=-1)&&ans[x][y-1]>ans[x][y]+s) { 78 ans[x][y-1]=ans[x][y]+s; 79 dfs(x,y-1,a[x][y-1]==-1?a[x][y]:a[x][y-1]); 80 } 81 } 82 } 83 84 int main() { 85 freopen("chess.in","r",stdin); 86 freopen("chess.out","w",stdout); 87 init(); 88 dfs(1,1,a[1][1]); 89 if (ans[m][m]==0x7F7F7F7F) 90 cout<<"-1"<<endl; 91 else 92 cout<<ans[m][m]<<endl; 93 fclose (stdin); 94 fclose (stdout); 95 return 0; 96 }
在10000个点中,最多只有1000个点有颜色,比较稀疏。所以可以使用另一种做法——最短路求解。
首先要知道一个概念,叫“曼哈顿距离”:
出租车几何或曼哈顿距离(Manhattan Distance)是由十九世纪的赫尔曼·闵可夫斯基所创词汇 ,是种使用在几何度量空间的几何学用语,用以标明两个点在标准坐标系上的绝对轴距总和。
在平面上,坐标(x1, y1)的i点与坐标(x2, y2)的j点的曼哈顿距离为:d(i,j)=|x1-x2|+|y1-y2|.
那么,怎么建图呢?依照题意,对于曼哈顿距离为1的两个格子,如果颜色相同,则连一条权值为0的无向边;若颜色不同,则连一条权值为1的无向边。同理,对于曼哈顿距离为2的两个格子,如果颜色相同,则连一条权值为2的无向边;若颜色不同,则连一条权值为3的无向边。然后就可以跑最短路了……吗?
题目当中仅保证起点有颜色,可没保证终点有颜色啊。如果终点没有颜色,那么怎么走最短路也走不了。所以对终点要有特判:如果终点没有颜色,则要把与终点的曼哈顿距离为1的点向终点连一条权值为2的有向边。如果没做这一步,会一直输出“-1”,这就叫“细节决定成败”。
T4:跳房子(jump)
这题是第四题,对于我这种蒟蒻来讲是不可能AC的,所以先想着骗分。什么时候不能得到目标分数呢?如果所有正数分数都加起来还小于目标分数,那就得不到目标分数。所以先特判“-1”。
再看看这一题,我想起了NOIP2015提高组的“跳石头”,那题不是二分吗?这题也好像啊……于是果断使用二分求解。确定下来二分了,那么就来考虑,怎样判断二分的这个答案可不可以,显然使用动态规划。
dp[i]表示跳到第i个格子时所能得到的分数最大值,若跳不到该格,则dp[i]=-1。为了动规方便,再加入起点的格子,显然,起点格子离原点的距离为0,格子上的值也为0。状态转移方程:max(l,r)表示[l,r]区间内能跳到的格子中的最大值,则dp[i]=max(点i到原点的距离-mid,点i到原点的距离+mid)。用没有优化的DP,我交上了我的考场代码。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 6 const int maxn=500000; 7 struct gezi { 8 int juli; 9 int zhi; 10 } a[maxn+1]; 11 long long dp[maxn+1]; 12 int n,d,k,lbound=1,rbound,ans; //考场上写的代码太粗心,此处有误,二分的左边界应该为0,幸好官方数据没有答案为0的数据 13 long long sum; 14 15 void kuaidu(int &p) { 16 char c; 17 int f=1; 18 p=0; 19 do { 20 c=getchar(); 21 if (c==‘-‘) 22 f=-1; 23 } while (c<‘0‘||c>‘9‘); 24 do p=p*10+c-‘0‘, c=getchar(); 25 while (c>=‘0‘&&c<=‘9‘); 26 p=p*f; 27 } 28 29 void init() { 30 cin>>n>>d>>k; 31 for (int i=1; i<=n; i++) { 32 kuaidu(a[i].juli); 33 kuaidu(a[i].zhi); 34 if (a[i].zhi>0) 35 sum+=a[i].zhi; 36 } 37 rbound=a[n].juli; 38 } 39 40 long long dynamic_programming(int zuo, int you) { 41 memset(dp,-1,sizeof(dp)); 42 dp[0]=0; 43 for (int i=1; i<=n; i++) 44 for (int j=0; j<i; j++) 45 if (a[i].juli-a[j].juli>=zuo&&a[i].juli-a[j].juli<=you&&dp[j]!=-1) 46 dp[i]=max(dp[i],dp[j]+a[i].zhi); 47 long long num=0; 48 for (int i=1; i<=n; i++) 49 if (dp[i]>num) 50 num=dp[i]; 51 return num; 52 } 53 54 int main() { 55 freopen("jump.in","r",stdin); 56 freopen("jump.out","w",stdout); 57 init(); 58 if (sum<k) { 59 cout<<"-1"<<endl; 60 return 0; 61 } 62 while (lbound<=rbound) { 63 int mid=(lbound+rbound)/2; 64 int zuobianjie=max(1,d-mid); 65 int youbianjie=d+mid; 66 long long num=dynamic_programming(zuobianjie,youbianjie); 67 if (num<k) 68 lbound=mid+1; 69 else { 70 ans=mid; 71 rbound=mid-1; 72 } 73 } 74 cout<<ans<<endl; 75 fclose (stdin); 76 fclose (stdout); 77 return 0; 78 }
这样DP的时间复杂度是二维的,对于50%的数据可以过,但是对于另外50%的数据n=500000,即使两秒的时限也是超得不爱超了。考后,我在想怎么优化动态规划。
我听到一个叫单调队列的东西,专门取区间内的最大最小值。在POJ上,有一题叫做“Sliding Window”,就是单调队列的入门题。单调队列要想优化DP,必须得保证l和r是单调递增或递减的。而在本题中,在向右DP时,上述的状态转移方程在dp[i]=max(点i到原点的距离-mid,点i到原点的距离+mid)的mid不变的情况下,随着i离原点越来越远,l和r越来越大,所以也是单调递增的。这样一优化,DP的时间复杂度就降至一维,对于最大的n=500000,就能在2秒的时限内轻松通过了。
改进后的DP代码:
1 long long dynamic_programming(int zuo, int you) { 2 memset(dp,-1,sizeof(dp)); 3 dp[0]=0; 4 memset(q,0,sizeof(q)); 5 int tou=1, wei=0, j=0; 6 for (int i=1; i<=n; i++) { 7 while (a[i].juli-a[j].juli>=zuo&&j<i) { 8 if (dp[j]!=-1) { 9 while (tou<=wei&&dp[q[wei]]<=dp[j]) 10 wei--; 11 q[++wei]=j; 12 } 13 j++; 14 } 15 while (tou<=wei&&a[i].juli-a[q[tou]].juli>you) 16 tou++; 17 if (tou<=wei) 18 dp[i]=dp[q[tou]]+a[i].zhi; 19 } 20 long long num=0; 21 for (int i=1; i<=n; i++) 22 if (dp[i]>num) 23 num=dp[i]; 24 return num; 25 }
总体而言,这次NOIP普及组没有什么算法,考的都是细节与技巧,全比赛难度最大的只是单调队列,而且单调队列在今年的夏令营里也讲过了,应当会灵活运用。只要在考场上够专注、认真,有充足的自信与扎实的编程基础,350分绝对不是问题。
以上是关于NOIP2017普及组解题报告的主要内容,如果未能解决你的问题,请参考以下文章