首页 > 代码库 > 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 }
View Code

 

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 }
View Code

反思:

这题在考场上把我坑了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 }
View Code

反思:

考场上想到了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 }
View Code

反思:

考场上思路有点飘逸,结果费了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 }
View Code

反思:

可能是没见过这种题吧,当时只想到了堆乱搞。也从一个侧面反映出自己做题太少,何况很多没见过这种题的也想到队列了呢……

 

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 }
View Code

反思:

第一眼看成了搜索,尝试剪枝浪费了很多时间。自己弱不能怪社会。

 

这次联赛,算是对自己一年多OI生涯的检验。结果自己却挂了,失误很多。(不知道这些到底该说是失误还是能力问题。)

别的,真的不想再说什么了。不敢再想。

很快就是WC,然后就是省选。结果如何,只能看自己了。

Noip2016 总结&反思