NOIP2014提高组总结
-by mps
尽管今年没参加NOIP2014提高组,但是做了一下,还是有感受的,在这里写出我500分的思路(满分以后会更改,毕竟能力有限......)
Day 1
T1 生活大爆炸版石头剪子布
【题目大意】
石头剪子布大家都玩过,只不过这题加了“斯波克”和“蜥蜴人”,事实上还是蛮简单的,有基本逻辑推理常识和基本代码处理能力即可AC,放在PJ都是第一题的难度...
一般有三种做法:
文艺青年:写个矩阵来表示得失,注意要判断两次(甲对乙及乙对甲)
普通青年:16个if嵌套
二B青年:25个if无嵌套
100分到手
代码:
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 const int MaxN=201; 8 const int f[5][5]={0,0,1,1,0, 9 1,0,0,1,0, 10 0,1,0,0,1, 11 0,0,1,0,1, 12 1,1,0,0,0}; 13 int a[MaxN],b[MaxN]; 14 int na,nb,n; 15 16 void readfile(){ 17 freopen("rps.in","r",stdin); 18 freopen("rps.out","w",stdout); 19 scanf("%d%d%d",&n,&na,&nb); 20 int i; 21 for(i=1;i<=na;i++) 22 scanf("%d",&a[i]); 23 for(;i<=n;i++)a[i]=a[i-na]; 24 for(i=1;i<=nb;i++) 25 scanf("%d",&b[i]); 26 for(;i<=n;i++)b[i]=b[i-nb]; 27 } 28 29 void solve(){ 30 int i,sa=0,sb=0; 31 for(i=1;i<=n;i++){ 32 if(f[a[i]][b[i]]==1)sa++; 33 else if(f[b[i]][a[i]]==1)sb++; 34 } 35 printf("%d %d",sa,sb); 36 } 37 38 int main(){ 39 readfile(); 40 solve(); 41 return 0; 42 }
T2 联合权值
【题目大意】
给你一张图,用无向边来联通这个图,每一条边的长度均为1,求距离为2的点的权值乘积的最大值和总和
难点来了:对于30%的数据 1<n≤100
对于50%的数据 1<n≤2000
对于100%的数据 1<n≤200000
拿到题目有一种很明显的算法:
求出每对顶点的距离,然后O(N^2)枚举,求和找最大值
具体实现就是floyed,O(N^3+N^2),30分
我们发现没有必要去求每一对顶点的距离,所以任选一点作为起点,bfs一遍,然后两个点的距离就可以用类似前缀和的形式求出
时间复杂度O(N^2),50分
事实上题目并没有这么难,我们进行数学分析
题目的意思很明显就是求一个点的前驱和后继的乘积和以及最大值
我们设x表示中间的点,它的前驱是a,b,c,后继是d,e,f
这里有一点要注意,并不是a只能和def配对,还可以和b,c配对
那么怎么求呢?
正好初二,学了整式的乘法
(1) (ab+ac+ad+ae+af)+(ba+bc+bd+be+bf)+(ca+cb+cd+ce+cf)+(da+db+dc+de+df)+(ea+eb+ec+ed+ef)+(fa+fb+fc+fd+fe)
(2) (a+b+c+d+e+f)^2
(2)-(1),得
=-(2ab+2ac+2bc+2ad+2ae+2af+2bd+2be+2bf+2cd+2ce+2cf+2de+2df+2ef-aa-2ab-2ac-2ad-2ae-2af-bb-2bc-2bd-2be-2bf-2cd-2ce-2cf-...-2ef)
=aa+bb+cc+dd+ee+ff
所以我们还需要填补每一项的平方
那么对于一个点,通过它能产生的联合权值=(∑前驱+∑后继)^2-{∑[(前驱后继的每一个)^2]}
如何计算最大值?更简单!只要弄两个域来控制即可
显然我们用中间结构体来存储。
100分到手!
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MaxN=200001; const int Mod=10007; struct READ{//离线处理 int u,v; }data[MaxN]; struct element{ int pri_sum,ne_sum; int w; int rec; int m1,m2; element(){rec=m1=m2=pri_sum=ne_sum=0;} }a[MaxN]; int n; int ans,MAX; void readfile(){ freopen("link.in","r",stdin); freopen("link.out","w",stdout); scanf("%d",&n); int i,u,v; for(i=1;i<n;i++) scanf("%d %d",&data[i].u,&data[i].v); for(i=1;i<=n;i++) scanf("%d",&a[i].w); for(i=1;i<n;i++){ a[data[i].u].ne_sum=(a[data[i].u].ne_sum+a[data[i].v].w)%Mod; a[data[i].v].pri_sum=(a[data[i].v].pri_sum+a[data[i].u].w)%Mod; a[data[i].u].rec=(a[data[i].u].rec+a[data[i].v].w*a[data[i].v].w)%Mod; a[data[i].v].rec=(a[data[i].v].rec+a[data[i].u].w*a[data[i].u].w)%Mod; if(a[data[i].u].w>a[data[i].v].m1){a[data[i].v].m2=a[data[i].v].m1;a[data[i].v].m1=a[data[i].u].w;} else if(a[data[i].u].w>a[data[i].v].m2)a[data[i].v].m2=a[data[i].u].w; if(a[data[i].v].w>a[data[i].u].m1){a[data[i].u].m2=a[data[i].u].m1;a[data[i].u].m1=a[data[i].v].w;} else if(a[data[i].v].w>a[data[i].u].m2)a[data[i].u].m2=a[data[i].v].w; } } void solve(){ int i,j,k; for(i=1;i<=n;i++){ MAX=max(MAX,(a[i].m1*a[i].m2)); ans=(ans+(a[i].ne_sum+a[i].pri_sum)*(a[i].ne_sum+a[i].pri_sum)-a[i].rec)%Mod; } printf("%d %d",MAX,ans); } int main(){ readfile(); solve(); return 0; }
T3 飞扬的小鸟
话说基本上每年都有一道游戏题→_→
【题目描述】
Flappy Bird 是一款风靡一时的休闲手机游戏。玩家
需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让
小鸟顺利通过画面右方的管道缝隙。如果小鸟一不小心撞到了
水管或者掉在地上的话,便宣告失败。
为了简化问题,我们对游戏规则进行了简化和改编:
1. 游戏界面是一个长为n,高 为m的二维平面,其中有
k个管道(忽略管道的宽度)。
2. 小鸟始终在游戏界面内移动。小鸟从游戏界面最左边
任意整数高度位置出发,到达游戏界面最右边时,游
戏完成。
3. 小鸟每个单位时间沿横坐标方向右移的距离为1,竖直移动的距离由玩家控制。如
果点击屏幕,小鸟就会上升一定高度X,每个单位时间可以点击多次,效果叠加;
如果不点击屏幕,小鸟就会下降一定高度Y。小鸟位于横坐标方向不同位置时,上
升的高度X和下降的高度Y可能互不相同。
4. 小鸟高度等于0或者小鸟碰到管道时,游 戏 失 败 。小 鸟 高 度 为m时,无法再上升。
现在,请你判断是否可以完成游戏。如果可以,输出最少点击屏幕数;否则,输出小鸟
最多可以通过多少个管道缝隙。
拿到题目仔细阅读后可以发现DP做法
我们设f(i,j)表示鸟飞到横坐标为i,纵坐标为j的位置时所需要的最少触屏次数
考虑了前后决策,我们发现不可能由前一阶段推到当前阶段,因为题目说:“遇到顶部不再上升”,所以我们可以用本阶段考虑下一阶段。
f(i,j-down[i]) (j-down[i]>0)
f(i+1,j)=min{ f(i,j+up[i]*k)+k (j+up[i]*k<=m,1<=k) {0<=i<n 1<=j<=m}
f(i,m)+k (j+up[i]*k>m)
时间复杂度O(N*M^2),70分到手
本蒟蒻只会这种70分算法,希望大牛能够在评论区教导100分算法,感激不尽
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MaxN=10005; const int MaxM=1005; int f[MaxN][MaxM];//f(i,j)表示小鸟飞到(i,j)这个位置所需的最少触屏次数 int n,m,PIPE; bool board[MaxN][MaxM]; int up[MaxN],down[MaxN]; int x[MaxN]; void init(){ freopen("bird.in","r",stdin); freopen("bird.out","w",stdout); scanf("%d%d%d",&n,&m,&PIPE); int i,upx,downx,j; for(i=0;i<n;i++) scanf("%d%d",&up[i],&down[i]); memset(board,false,sizeof(board)); for(i=1;i<=PIPE;i++) { scanf("%d%d%d",&x[i],&upx,&downx); for(j=0;j<=upx;j++) board[x[i]][j]=true; for(j=downx;j<=m;j++) board[x[i]][j]=true; } } int minx; void ok(){printf("1\n%d",minx);} void no(){ int pas=0,i,j; for(i=n-1;i>=0;i--) { for(j=0;j<=m;j++) if(f[i][j]!=0x3f3f3f3f)break; if(j<=m) { for(j=1;j<=PIPE;j++) if (x[j]<=i) pas++; printf("0\n%d",pas); return; } pas=0; } } /*动态转移方程: f(i,j)=min{f(i-1,j+down[i-1]),min{f(i-1,j-up[i-1]*k)}} (1<=i<=n,0<=j<=m,j-up[i-1]*k>=0) 特别地,f(0,i)=0 {0<=i<=m} */ void dp(){ int i,j,k,l,sumw=0; memset(f,0x3f3f3f3f,sizeof(f)); for(i=0;i<=m;i++)f[0][i]=0; for(i=0;i<n;i++) for(j=1;j<=m;j++) if(board[i][j]==false) { if(j-down[i]>0 && board[i+1][j-down[i]]==false) f[i+1][j-down[i]]=min(f[i+1][j-down[i]],f[i][j]); for(k=1;k<=m;k++) if(j+up[i]*k<=m && board[i+1][j+up[i]*k]==false) f[i+1][j+up[i]*k]=min(f[i][j]+k,f[i+1][j+up[i]*k]); else if(j+up[i]*k>m && board[i+1][m]==false)//上升至顶部不再上升 { f[i+1][m]=min(f[i+1][m],f[i][j]+k); break; } } minx=0x3f3f3f3f; for(i=1;i<=m;i++) minx=min(minx,f[n][i]); if(minx==0x3f3f3f3f)no(); else ok(); } int main(){ init(); dp(); return 0; }
Day 1总结
100/100/70分到手,其实题目很水,不过如果去考场考的话,估计会严重缩水
Day 2
T1 无线网路发射器设置
【题目描述】
在[0,128][0,128]的矩阵中找到一个位置,使得以它为中心所产生的边长为2*d的正方形能够覆盖最多公共区域
很简单的一道题目,朴素枚举然后O(N)扫描一趟,由于N≤20,所以128*128*20也是无压力水过
还记得有位大牛当场码了一个树状数组套树状数组求和,然后速度飞快,不过有必要么?
100分到手
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MaxN=21; const int MaxS=128; int x[MaxN],y[MaxN],k[MaxN]; int n,d; void readfile(){ freopen("wireless.in","r",stdin); freopen("wireless.out","w",stdout); scanf("%d%d",&d,&n); for(int i=1;i<=n;i++) scanf("%d%d%d",&x[i],&y[i],&k[i]); } void solve(){ int i,j,kk,res=0,num=0,calc=0; for(i=0;i<=MaxS;i++) for(j=0;j<=MaxS;j++){ calc=0; for(kk=1;kk<=n;kk++) if(i-d<=x[kk] && j-d<=y[kk] && j+d>=y[kk] && i+d>=x[kk]) calc+=k[kk]; if(calc>res){ res=calc; num=1; } else if(calc==res) num++; } printf("%d %d",num,res); } int main(){ readfile(); solve(); return 0; }
T2 寻找道路
【题目描述】
在有向图G中,每条边的长度均为1,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件:
1.路径上的所有点的出边所指向的点都直接或间接与终点连通。
2.在满足条件1的情况下使路径最短。
注意:图G中可能存在重边和自环,题目保证终点没有出边。
请你输出符合条件的路径的长度
没什么难的,水2次SPFA,第一次在反向图中跑,只要到达某个点的次数不等于其在正向图中的出度,就标记为1,否则为0
第二次直接一遍SPFA求单对定点最短路,切记不可走标记为1的点
无压力AC
100分到手
#include <iostream> using namespace std; #include <cstdio> #include <deque> #include <queue> #include <cstring> const int MaxN=10001; const int L=1500; struct point{ int v[L]; int len; point(){len=0;} }g[MaxN],f[MaxN]; int dis[MaxN]; int flag[MaxN]; int n,m,s,t; void init(){ freopen("road.in","r",stdin); freopen("road.out","w",stdout); scanf("%d%d",&n,&m); int i,u,v; for(i=1;i<=m;i++){ scanf("%d %d",&u,&v); g[u].v[++g[u].len]=v; f[v].v[++f[v].len]=u; } scanf("%d%d",&s,&t); } deque<int> q; void spfa(int u){ int head,i; memset(dis,0x3f3f3f3f,sizeof(dis)); dis[u]=0; q.push_front(u); while(!q.empty()){ head=q.front(); q.pop_front(); for(i=1;i<=g[head].len;i++){ if(dis[head]+1<dis[g[head].v[i]] && flag[g[head].v[i]]==1){ dis[g[head].v[i]]=dis[head]+1; if(dis[head]>=dis[g[head].v[i]])q.push_front(g[head].v[i]); else q.push_back(g[head].v[i]); } } } } deque<int> Q; int cnt[MaxN]; void spfa2(int u){ int head,i; memset(dis,0x3f3f3f3f,sizeof(dis)); dis[u]=0; Q.push_front(u); while(!Q.empty()){ head=Q.front(); Q.pop_front(); for(i=1;i<=f[head].len;i++){ cnt[f[head].v[i]]++; if(dis[head]+1<dis[f[head].v[i]]){ dis[f[head].v[i]]=dis[head]+1; if(dis[head]>=dis[f[head].v[i]])Q.push_front(f[head].v[i]); else Q.push_back(f[head].v[i]); } } } for(i=1;i<=n;i++) if(cnt[i]==g[i].len || i==t)flag[i]=1; } void solve(){ memset(flag,0,sizeof(flag)); int i; spfa2(t); if(!flag[s]){cout<<-1;return;} spfa(s); cout<<dis[t]; } int main(){ init(); solve(); return 0; }
T3 解方程
【题目大意】
对于一个方程
a0+a1*x+a2*x^2+a3*x^3+...+an*x^n=0
求其在[1,m]的解的个数以及所有解
数据范太恐怖了,不想写→_→
首先Orz @LZZ大神高一AK 提高组,而且能做出这题实在是NB
作为蒟蒻的我只能拿30分,如果不嫌烦的话可以试试70分,高精度+压位
听某神牛说,只要模一个大质数然后求模意义下的解即可,居然还有70分,我也是醉了
100分听LZZ大神说是FFT+秦九韶
代码就不水了,话说拿去问数学老师,老师果断回绝了我→_→
Day 2总结
100/100/30 比day1差了一些,主要有T3在,= =,数学不过关啊
NOIP2014提高组总结
初二这个年级不适合参加提高组,所以就去参加PJ了,还给坑了→_→,下次不和老师玩了。。
今年可能是由于面向社会征题的原因,异常的水,安徽的分数线是375分,我500分,顺利1=(真后悔听老师的)
其实每一天的前两题都不难,主要靠基础能力,而第三题则涉及一些诡异算法,还好飞扬的小鸟的DP方程很好想,又拿了100分
只要做出每天前两题,其余题目能骗分就骗分,能暴力就暴力,1等是没有问题的