首页 > 代码库 > Noip2016 总结&反思
Noip2016 总结&反思
一直在期盼的联赛,真正来临时,却远不像我想象的样子。
有些事,真的不敢再想。
算法可以离线,时光却不能倒流。dfs可以回溯,现实却没有如果。
有些事,注定只能成为缺憾,抱恨终生。
不得不说今年Noip画风大变。
一是题目难度不循常规。D1难度如此之大,估计很多人都会很紧张。(比如我,下了场以为要有一群250+的,感觉自己要AFO了。)并且,D1T2居然比D1T3还难(D2也是如此),把好多人给坑了(估计很多人都是死磕T2不放结果T3的76分暴力都没时间写了)。
PS:感觉题目难度应该是这样的,D1T1<D2T1<D1T3<D2T3<D2T2<D1T2
二是题目类型有所变化。今年的题基本没看见模板题,都是一些比较考察思维的题。
“综合来看,相比去年的题目偏重考察知识点,今年的题目在思维难度上有了明显的提升,可以有效的避免“东西学得多,分就高”的情况。在我看来,NOIP 虽然是一个普及形式的比赛,涉及知识点也有限,但并不意味着东西学得多就能 AK,竞赛竞赛,竞的是思维,赛的是心态,而不是仅仅拘泥于学习知识点的多少、学习时间的长短。让所有人都有收获,这可能是我心中比赛应有的样子吧。”——Sengxian
D1T2成功把自己坑了,考场上思路飘逸到树剖树分治和主席树,连一道树上差分都想不出来,都是因为思维能力太垃圾。还有D2T2(是怪我没学过Huffman树不知道用队列做的方法么……)。
联赛比的确实是思维和心态。
自己心态也比较差,考场上没能想出D1T2(放到平常考试估计想个1h也能想出来……),暴力还写挂了,分数-=15;D1T3本来想到正解了,然而脑残的以为最后一维要开3,死活过不去(真是SB,我为什么不试试开2会怎样);D2T1都能飘逸到Lucas定理,真是醉了;一直在叮嘱自己注意卡常,结果D2T2的make_heap()众给我贡献了2T,分数-=10;D2T3一眼看上去居然看成了搜索,明明不会剪枝还硬剪了半个小时,真是废了(放到平常考试估计很快就能看出状压的……)。
至于思维,向来是我的弱项。以前总是在学一些高端的东西,平衡树、主席树、树套树、CDQ……然而这些东西联赛都没能用上(平时最实用的主席树到了这儿也没能用上……),反而学了这些东西之后做了好多模板题,对思维的锻炼就很少了。
联赛的6道题,D1T1和D2T1两道水题水过去了,D2T3一道状压傻逼题卡过去了,D1T2的树上差分、D1T3的期望DP和D2T2的队列都没想出来,写的暴力。
一场考试,6道题,我居然写了3个暴力,真是废了。幸亏暴力分给的足,要不然我早就AFO了。
现在离省选也没多少天了。唯一期盼的就是黑暗的高考生活赶快结束,WC和省选集训早日到来。
言归正传。
D1T1 玩具谜题
水题不解释。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=100010; 6 int n,m,a[maxn],x,d,cur=0; 7 char s[maxn][15]; 8 int main(){ 9 scanf("%d%d",&n,&m); 10 scanf("%d%s",&a[0],s[0]); 11 for(int i=n-1;i;i--)scanf("%d%s",&a[i],s[i]); 12 while(m--){ 13 scanf("%d%d",&x,&d); 14 if(x==a[cur])cur=(cur+d)%n; 15 else cur=(cur-d+n)%n; 16 } 17 printf(s[cur]); 18 return 0; 19 }
D1T2 天天爱跑步
感觉这种题就该放在D2T3……
首先把每条路径(u,v)拆成两条路径(u,LCA)和(LCA,v)后分别考虑,然后对于每个x,考虑哪些点会对ans[x]产生贡献。
设每个点的深度为d,显然只有满足以下条件的u和v会对x的答案产生贡献:
(对u来说)d[u]-d[x]=w[x]
(对v来说)d[u]-d[LCA]+d[x]-d[LCA]=w[x]
把式子变形,得
d[u]=w[x]+d[x]
d[u]-2*d[LCA]=w[x]-d[x]
这样左边的值就不随x而改变,因为只有单点修改单点查询,用两个数组分别维护即可。
显然对于一个点x,只有(u,v)有一个在x的子树中的路径才能对ans[x]产生贡献。因此我们可以用树上差分,每个修改在u和v处进行,在LCA处撤销,在dfs到一个点之前记录答案,与dfs结束后的答案相减即为子树中的贡献之和。
可能是我写的比较渣,如果对LCA处有贡献的话会算重一次,需要手动去重。
LCA我写的是树剖,后来重写一遍Tarjan结果写挂了,算了反正树剖也挺快我就这么着吧……
细节问题:
a[]维护u的贡献,b[]维护v的贡献。
因为所有u的贡献都一样,直接压缩到cntu[]中表示这个点作为u的次数即可。
u[]是对LCA记录所有u(因为v的贡献只与u有关,所以不用记录v到底是谁),v[]是对v记录所有u和LCA。
因为d[u]-2*d[LCA]可能是负的,因此加上一个2*n的偏移量。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 using namespace std; 6 const int maxn=300010; 7 void dfs1(int); 8 void dfs2(int); 9 int LCA(int,int); 10 void dfs(int); 11 int prt[maxn],size[maxn],d[maxn],son[maxn],top[maxn]; 12 vector<int>G[maxn],u[maxn]; 13 struct pir{int u,lca;pir(int u,int lca):u(u),lca(lca){}}; 14 vector<pir>v[maxn]; 15 int n,m,w[maxn],cntu[maxn]={0},ans[maxn]={0},x,y; 16 int a[maxn]={0},b[maxn<<2]={0}; 17 int main(){ 18 scanf("%d%d",&n,&m); 19 for(int i=1;i<n;i++){ 20 scanf("%d%d",&x,&y); 21 G[x].push_back(y); 22 G[y].push_back(x); 23 } 24 dfs1(1);dfs2(1); 25 for(int i=1;i<=n;i++)scanf("%d",&w[i]); 26 while(m--){ 27 scanf("%d%d",&x,&y); 28 int lca=LCA(x,y); 29 cntu[x]++; 30 u[lca].push_back(x); 31 v[y].push_back(pir(x,lca)); 32 if(d[lca]+w[lca]==d[x])ans[lca]--; 33 } 34 dfs(1); 35 for(int i=1;i<=n;i++)printf("%d ",ans[i]); 36 return 0; 37 } 38 void dfs1(int x){ 39 size[x]=1; 40 for(int i=0;i<(int)G[x].size();i++)if(G[x][i]!=prt[x]){ 41 prt[G[x][i]]=x; 42 d[G[x][i]]=d[x]+1; 43 dfs1(G[x][i]); 44 size[x]+=size[G[x][i]]; 45 if(size[G[x][i]]>size[son[x]])son[x]=G[x][i]; 46 } 47 } 48 void dfs2(int x){ 49 if(x==son[prt[x]])top[x]=top[prt[x]]; 50 else top[x]=x; 51 for(int i=0;i<(int)G[x].size();i++)if(G[x][i]!=prt[x])dfs2(G[x][i]); 52 } 53 int LCA(int x,int y){ 54 while(top[x]!=top[y]){ 55 if(d[top[x]]<d[top[y]])swap(x,y); 56 x=prt[top[x]]; 57 } 58 return d[x]<d[y]?x:y; 59 } 60 void dfs(int x){ 61 ans[x]-=a[d[x]+w[x]]+b[w[x]-d[x]+(n<<1)]; 62 a[d[x]]+=cntu[x]; 63 for(int i=0;i<(int)v[x].size();i++)b[d[v[x][i].u]-(d[v[x][i].lca]<<1)+(n<<1)]++; 64 for(int i=0;i<(int)G[x].size();i++)if(G[x][i]!=prt[x])dfs(G[x][i]); 65 ans[x]+=a[d[x]+w[x]]+b[w[x]-d[x]+(n<<1)]; 66 for(int i=0;i<(int)u[x].size();i++){ 67 a[d[u[x][i]]]--; 68 b[d[u[x][i]]-(d[x]<<1)+(n<<1)]--; 69 } 70 }
反思:
这题在考场上把我坑了0.5+h,当时什么都想,偏偏想不出来正解……加之前一天晚上睡得很不踏实,有点困,最终还是没想出来,打的60分暴力。结果有15分写挂了,真是废了……
反映出自己思维能力过弱,碰到稍微有点难度的题就想不出来。
一定要少刷模板题。
D1T3 换教室
期望DP模板题,我特么居然跪了……
设f[i][j][0/1]表示前i节课选了j节,0/1表示这节课选了没有的期望值。手玩各种转移就行了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2010,maxv=310; 6 int n,m,v,e,c[maxn],d[maxn],dis[maxn][maxn],x,y,z; 7 double p[maxn],f[maxn][maxn][2],ans=1e50; 8 int main(){ 9 memset(dis,63,sizeof(dis)); 10 scanf("%d%d%d%d",&n,&m,&v,&e); 11 m=min(n,m); 12 for(int i=1;i<=v;i++)dis[i][i]=0; 13 for(int i=1;i<=n;i++)scanf("%d",&c[i]); 14 for(int i=1;i<=n;i++)scanf("%d",&d[i]); 15 for(int i=1;i<=n;i++)scanf("%lf",&p[i]); 16 while(e--){ 17 scanf("%d%d%d",&x,&y,&z); 18 if(z<dis[x][y])dis[x][y]=dis[y][x]=z; 19 } 20 for(int k=1;k<=v;k++)for(int i=1;i<=v;i++)for(int j=1;j<=v;j++)dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]); 21 for(int i=0;i<=n;i++)for(int j=0;j<=m;j++)f[i][j][0]=f[i][j][1]=1e50; 22 f[1][0][0]=f[1][1][1]=0.0; 23 for(int i=2;i<=n;i++)for(int j=0;j<=i&&j<=m;j++){ 24 f[i][j][0]=min(f[i-1][j][0]+(double)dis[c[i-1]][c[i]],f[i-1][j][1]+p[i-1]*dis[d[i-1]][c[i]]+(1.0-p[i-1])*dis[c[i-1]][c[i]]); 25 if(j>0)f[i][j][1]=min(f[i-1][j-1][0]+p[i]*dis[c[i-1]][d[i]]+(1.0-p[i])*dis[c[i-1]][c[i]],f[i-1][j-1][1]+p[i-1]*p[i]*dis[d[i-1]][d[i]]+(1.0-p[i-1])*p[i]*dis[c[i-1]][d[i]]+p[i-1]*(1.0-p[i])*dis[d[i-1]][c[i]]+(1.0-p[i-1])*(1.0-p[i])*dis[c[i-1]][c[i]]); 26 else f[i][j][1]=1e50; 27 } 28 for(int j=0;j<=m;j++)ans=min(ans,min(f[n][j][0],f[n][j][1])); 29 printf("%.2lf",ans); 30 return 0; 31 }
反思:
考场上想到了0/1,然后我脑残的以为应该分没选、选了成功和选了失败三种情况讨论,结果死调不出来,还是写的m<=2的76分暴力,这次倒是没写挂……
第一次好好写期望DP,被干穿了……自己弱不能怪社会……
D2T1 组合数问题
杨辉三角+二维前缀和随便水。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=2010; 6 int q,n,m,p,c[maxn][maxn]={{0}},a[maxn][maxn]={{0}}; 7 int main(){ 8 scanf("%d%d",&q,&p); 9 c[0][0]=1; 10 for(int i=1;i<=2000;i++){ 11 c[i][0]=1; 12 for(int j=1;j<=i;j++){ 13 c[i][j]=(c[i-1][j]+c[i-1][j-1])%p; 14 if(!c[i][j])a[i][j]=1; 15 } 16 } 17 for(int i=1;i<=2000;i++){ 18 a[i][0]+=a[i-1][0]; 19 a[0][i]+=a[0][i-1]; 20 for(int j=1;j<=2000;j++)a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1]; 21 } 22 while(q--){ 23 scanf("%d%d",&n,&m); 24 printf("%d\n",a[n][m]); 25 } 26 return 0; 27 }
反思:
考场上思路有点飘逸,结果费了10min……虽然费的时间不算多吧……
D2T2 蚯蚓
首先把所有蚯蚓变长改成切出来的蚯蚓变短,然后用三个队列分别维护原来的蚯蚓、砍出来的长的那段和短的那段,显然三个队列都是单调的,这样就可以线性了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<functional> 5 #include<queue> 6 #define frt(que) ((que.empty()?-2147483647:que.front())) 7 using namespace std; 8 int n,m,t,u,v,q,o[100010],x,a,b,c; 9 queue<int>qa,qb,qc; 10 int main(){ 11 scanf("%d%d%d%d%d%d",&n,&m,&q,&u,&v,&t); 12 for(int i=1;i<=n;i++)scanf("%d",&o[i]); 13 sort(o+1,o+n+1,greater<int>()); 14 for(int i=1;i<=n;i++)qa.push(o[i]); 15 for(int i=1;i<=m;i++){ 16 a=frt(qa); 17 b=frt(qb); 18 c=frt(qc); 19 if(a>=b&&a>=c)qa.pop(); 20 else if(b>c){ 21 a=b; 22 qb.pop(); 23 } 24 else{ 25 a=c; 26 qc.pop(); 27 } 28 a+=q*(i-1); 29 x=(long long)a*u/v; 30 qb.push(x-q*i); 31 qc.push(a-x-q*i); 32 if(i%t==0)printf("%d ",a); 33 } 34 printf("\n"); 35 for(int i=1;!(qa.empty()&&qb.empty()&&qc.empty());i++){ 36 a=frt(qa); 37 b=frt(qb); 38 c=frt(qc); 39 if(a>=b&&a>=c)qa.pop(); 40 else if(b>c){ 41 a=b; 42 qb.pop(); 43 } 44 else{ 45 a=c; 46 qc.pop(); 47 } 48 a+=q*m; 49 if(i%t==0)printf("%d ",a); 50 } 51 return 0; 52 }
反思:
可能是没见过这种题吧,当时只想到了堆乱搞。也从一个侧面反映出自己做题太少,何况很多没见过这种题的也想到队列了呢……
D2T3 愤怒的小鸟
状压傻逼题。
话说各种被uoj hack,一开始eps设成1e-7被hack了,改成1e-8又被hack了,改成1e-9还被hack了,改成1e-10就A了,uoj这hack数据也太丧病了吧……
那个cnt_tbl是用来统计1数量卡边界用的,话说这玩意儿还挺好使……
1 #include<cstdio> 2 #include<cstring> 3 #include<cmath> 4 #include<algorithm> 5 #define popcnt(x) (cnt_tbl[(x)>>16]+cnt_tbl[(x)&65535]) 6 using namespace std; 7 const int maxn=25; 8 const double eps=1e-10; 9 double dcmp(double); 10 double x[maxn],y[maxn]; 11 int T,n,f[1<<19],cnt_tbl[70010]={0}; 12 int main(){ 13 for(int i=1;i<65536;i++)cnt_tbl[i]=cnt_tbl[i>>1]+(i&1); 14 scanf("%d",&T); 15 while(T--){ 16 memset(f,63,sizeof(f)); 17 for(int s=0;s<(1<<18);s++)f[s]=popcnt(s); 18 scanf("%d%*d",&n); 19 for(int i=1;i<=n;i++)scanf("%lf%lf",&x[i],&y[i]); 20 for(int i=1;i<=n;i++)for(int j=1;j<i;j++)if(dcmp(x[i]-x[j])){ 21 double a=(x[j]*y[i]-x[i]*y[j])/(x[i]*x[j]*(x[i]-x[j])),b=(x[j]*x[j]*y[i]-x[i]*x[i]*y[j])/(x[i]*x[j]*(x[j]-x[i])); 22 if(dcmp(a)>-1)continue; 23 int w=0; 24 for(int k=1;k<=n;k++)if(!dcmp(a*x[k]*x[k]+b*x[k]-y[k]))w|=(1<<(k-1)); 25 for(int s=0;s<(1<<n);s++)f[s|w]=min(f[s|w],f[s]+1); 26 } 27 printf("%d\n",f[(1<<n)-1]); 28 } 29 return 0; 30 } 31 double dcmp(double x){ 32 if(fabs(x)<eps)return 0; 33 return x<0.0?-1:1; 34 }
反思:
第一眼看成了搜索,尝试剪枝浪费了很多时间。自己弱不能怪社会。
这次联赛,算是对自己一年多OI生涯的检验。结果自己却挂了,失误很多。(不知道这些到底该说是失误还是能力问题。)
别的,真的不想再说什么了。不敢再想。
很快就是WC,然后就是省选。结果如何,只能看自己了。
Noip2016 总结&反思