本蒟蒻终于做完了$NOIP2016$!
继续写总结:
$Day1$:
$T1$:玩具谜题
大模拟题不解释。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<string> #define MAXN 100010 using namespace std; int n,m,now=1; struct node1{ int inout; string name; }a[MAXN]; struct node2{ int lorr,num; }b[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } int main(){ n=read();m=read(); for(int i=1;i<=n;i++){ a[i].inout=read(); cin>>a[i].name; } for(int i=1;i<=m;i++){ b[i].lorr=read();b[i].num=read(); if(a[now].inout==1){ if(b[i].lorr==0){ now=(now+b[i].num)%n; } else{ now=(now-b[i].num+n)%n; } } else{ if(b[i].lorr==0){ now=(now-b[i].num+n)%n; } else{ now=(now+b[i].num)%n; } } if(now==0)now=n; } cout<<a[now].name<<endl; return 0; }
$T2$:天天爱跑步
这就是众所周知的$NOIP2016$的恶心题!
正解是$LCA$+树上差分+桶。。。
先定义几个数组:
$set[x]$:以$x$为$LCA$的路径的起点的集合。
$num[x]$:以$x$为路径起点的路径条数。
$set2[x]$:以$x$为终点的路径的起点集合。
$set3[x]$:以$x$为$LCA$的路径的终点的集合。
正解并不是对一个个玩家进行操作。
而是先对全部玩家进行一些预处理,然后用两个类似的$dfs$函数对整棵树处理。
最后再做一些微调,就输出答案。
对于玩家在树上的路径$<u,v>$,我们可以对其进行拆分:$u->LCA(u,v),LCA(u,v)->v$两条路径。
$LCA$我用了树链剖分。
我们先考虑$u->LCA(u,v)$这条路径,这是一条向“上”跑的路径。
对与这条路径上的点$i$来说,当且仅当$deep[i]+w[i]==deep[u]$时,$u$节点对$i$节点是有贡献的。
那么也就是说,只要符合$deep[i]+w[i]$的全部是玩家起点的点,就能对$i$点产生贡献。
$ans[i]$加上的其实就是$i$的子树对$i$的贡献,因为我们在处理好子树之后的,我们已经处理好了对$i$有影响的节点,
所以我们只要加上先后之间的桶差值就相当于统计了答案,其作用是删去桶中以$i$为$LCA$的路径的起点深度桶的值。
因为当我们遍历完$i$节点的孩子时,对于以$i$节点为LCA的路径来说,这条路径上的信息对$i$的祖先节点是不会有影响的。
所以要将其删去。
我们再来考虑向下的路径,即$LCA(u,v)->v$。
对于向“下”走的路径,我们也思考,在什么条件下,这条路径上的点会获得贡献呢?
很明显的,当$dis(u,v)-deep[v]==w[i]-deep[i]$的时候,这条路径才会对$i$点有贡献。
对于桶来说,我们在计算的过程中其下标可能是负值,所以我们在操作桶时要将其下标右移$n$,即点数。
如果一条路径的$LCA$能观察到这条路上的人,我们还需将该$LCA$去重。
即:$if(deep[u]==deep[lca]+w[i])ans[lca]--;$
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<vector> #define MAXN 300010 using namespace std; vector<int> set1[MAXN],set2[MAXN],set3[MAXN]; int n,m,c=1,d=1; int head[MAXN],deep[MAXN],son[MAXN],size[MAXN],fa[MAXN],id[MAXN],top[MAXN]; int w[MAXN],dis[MAXN],num[MAXN],ans[MAXN],devote[MAXN<<1]; bool vis[MAXN]; struct node1{ int next,to; }a[MAXN<<1]; struct node2{ int s,t,lca,dis; }b[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline void add(int x,int y){ a[c].to=y; a[c].next=head[x]; head[x]=c++; a[c].to=x; a[c].next=head[y]; head[y]=c++; } void dfs1(int rt){ son[rt]=0;size[rt]=1; for(int i=head[rt];i;i=a[i].next){ int will=a[i].to; if(!deep[will]){ deep[will]=deep[rt]+1; dis[will]=dis[rt]+1; fa[will]=rt; dfs1(will); size[rt]+=size[will]; if(size[son[rt]]<size[will])son[rt]=will; } } } void dfs2(int rt,int f){ id[rt]=d++;top[rt]=f; if(son[rt])dfs2(son[rt],f); for(int i=head[rt];i;i=a[i].next){ int will=a[i].to; if(will!=fa[rt]&&will!=son[rt]) dfs2(will,will); } } int LCA(int x,int y){ while(top[x]!=top[y]){ if(deep[top[x]]<deep[top[y]])swap(x,y); x=fa[top[x]]; } if(deep[x]>deep[y])swap(x,y); return x; } void dfs3(int rt){ vis[rt]=true; int x=devote[deep[rt]+w[rt]+n]; for(int i=head[rt];i;i=a[i].next){ int v=a[i].to; if(!vis[v])dfs3(v); } devote[deep[rt]+n]+=num[rt]; ans[rt]+=devote[deep[rt]+w[rt]+n]-x; int l=set1[rt].size(); for(int i=0;i<l;i++)devote[deep[set1[rt][i]]+n]--; vis[rt]=false; } void dfs4(int rt){ vis[rt]=true; int x=devote[w[rt]-deep[rt]+n]; for(int i=head[rt];i;i=a[i].next){ int v=a[i].to; if(!vis[v])dfs4(v); } int l=set2[rt].size(); for(int i=0;i<l;i++)devote[set2[rt][i]+n]++; ans[rt]+=devote[w[rt]-deep[rt]+n]-x; l=set3[rt].size(); for(int i=0;i<l;i++)devote[set3[rt][i]+n]--; vis[rt]=false; } void work(){ for(int i=1;i<=m;i++){ b[i].s=read();b[i].t=read(); b[i].lca=LCA(b[i].s,b[i].t); b[i].dis=dis[b[i].s]+dis[b[i].t]-dis[b[i].lca]*2; num[b[i].s]++; set1[b[i].lca].push_back(b[i].s); set2[b[i].t].push_back(b[i].dis-deep[b[i].t]); set3[b[i].lca].push_back(b[i].dis-deep[b[i].t]); } dfs3(1); dfs4(1); for(int i=1;i<=m;i++)if(deep[b[i].s]==deep[b[i].lca]+w[b[i].lca])ans[b[i].lca]--; for(int i=1;i<=n;i++)printf("%d ",ans[i]); } void init(){ int x,y; n=read();m=read(); for(int i=1;i<n;i++){ x=read();y=read(); add(x,y); } for(int i=1;i<=n;i++){ w[i]=read(); ans[i]=0; } deep[1]=1;dis[1]=0; dfs1(1); dfs2(1,1); } int main(){ init(); work(); return 0; }
$T3$:换教室
据说当时许多大佬看到概率期望就不做了。。。
其实暴力还是很好想的。
正解就需要一定水平了。
我表示我比较懒,题解戳这里。
总结:
并不知道$NOIP2016Day1$就考这么难干甚。。。
出题人一定被大佬们的口水淹了。。。
反正不能看到没学过算法的题就直接放弃,一般这种题出在$NOIP$,出题人都会给许多的部分分。
剩下的分就是专门防AK的。。。
$Day2$:
$T1$:组合数问题
看到题就知道把那个$k$当成模数,然后直接杨辉三角。
然后求区间内的树的话,套一个前缀和就好。
我好像写了一个$O(Tn)$的算法都过了。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 2010 using namespace std; long long mod,f[MAXN][MAXN],sum[MAXN][MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } void make(){ int m=MAXN-10; mod=read(); f[0][0]=f[1][0]=f[1][1]=1; sum[0][0]=sum[1][0]=sum[1][1]=0; for(int i=2;i<=m;i++){ for(int j=0;j<=i;j++){ f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod; sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+(f[i][j]==0?1:0); } sum[i][i+1]=sum[i][i]; } } int main(){ int n,m,t=read(); make(); while(t--){ n=read();m=read(); if(m>n)printf("%lld ",sum[n][n]); else printf("%lld ",sum[n][m]); } return 0; }
$T2$:蚯蚓
首先暴力用$STL$的堆膜你是有$70$分的!
然后正解比较厉害——题目中隐含条件。
这个条件是什么呢?
单调性!
我们发现先被切掉的蚯蚓分成的蚯蚓一定比后切掉的蚯蚓分成的蚯蚓大。
假设这两只蚯蚓分别为$l_a,l_b(a>b)$,那么它被切成$a_1,a_2$。
$t$秒后,$b$被切成了$b_1,b_2$。
此时$a_1,a_2$的长度为:$$l_{a_1}+t=pl_{a}+t,l_{a_2}+t=(1-p)$$
而$b_1,b_2$的长度却为:
$$p(l_b+t),(1-p)(1_b+t)$$
容易看出:$$l_{a_1}>l_{b_1},l_{a_2}>l_{b_2}$$
也就是说根本不需要用一个堆来维护, 它本来就具有一定单调性!
那么就是说如果蚯蚓$a_1,a_2,cdots,a_k$满足$a_1>a_2>cdots>a_k$,那么以此分成两只$a_{11},a_{12},a_{21},a_{22},cdots,a_{k1},a_{k2}$,则有:
$$a_{12}>a_{22}>cdots>a_{k2},a_{11}>a_{21}>cdots>a_{k1}$$
那么就可以将这两堆依次存储,加上还没被切过的蚯蚓。
每次要切时在这三堆里面选择最大的, 切完再依次放回去。
所以这么做时间复杂度为$O(m)$。
注意:千万不要用$STL$的$queue$,会$TLE$的!
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<queue> #define MAXN 7500010 #define MAX (1LL<<62) using namespace std; int n,m,t,Top=0; long long q,u,v,add=0,val[MAXN],ans[MAXN]; struct Queue{ int head,tail; long long v[MAXN]; Queue(){head=1;tail=0;} void push(long long x){v[++tail]=x;} void pop(){v[head++]=0;} bool empty(){return head>tail;} long long front(){return v[head];} }que_one,que_two,que_three; inline int read(){ int date=0;char c=0; while(c<'0'||c>'9')c=getchar(); while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date; } void write(long long x){ if(x>9)write(x/10); putchar(x%10+'0'); } inline bool cmp(const long long &p,const long long &q){ return p>q; } void push(long long x,long long y){ if(x<y)swap(x,y); que_two.push(x);que_three.push(y); } long long top(){ long long x1=-MAX,x2=-MAX,x3=-MAX; if(!que_one.empty())x1=que_one.front(); if(!que_two.empty())x2=que_two.front(); if(!que_three.empty())x3=que_three.front(); if(x1>=x2&&x1>=x3){ que_one.pop(); return x1; } else if(x2>=x1&&x2>=x3){ que_two.pop(); return x2; } else{ que_three.pop(); return x3; } } void work(){ long long x,y; for(int i=1;i<=m;i++){ ans[i]=top()+add; x=ans[i]*u/v; y=ans[i]-x; add+=q; push(x-add,y-add); } while(!que_one.empty()||!que_two.empty()||!que_three.empty())val[++Top]=top()+add; for(int i=t;i<=m;i+=t){write(ans[i]);putchar(' ');} putchar(' '); for(int i=t;i<=Top;i+=t){write(val[i]);putchar(' ');} putchar(' '); } void init(){ n=read();m=read();q=read();u=read();v=read();t=read(); for(int i=1;i<=n;i++)val[i]=read(); sort(val+1,val+n+1,cmp); for(int i=1;i<=n;i++)que_one.push(val[i]); } int main(){ init(); work(); return 0; }
$T3$:愤怒的小鸟
题目名称好评!
本蒟蒻唯一能想到的状压$DP$。。。
正解不想复制了,戳这里!
总结:
其实暴力分还是有很多的。
但是正解考察了许多细节。
这件事情告诉我们——要善于发掘题目中的隐含条件!
$NOIP2016$总结:
总的来说,暴力分还是有很多的。
我大概算了一下,满的暴力应该是$300$分。
当然,是建立在速度和质量均高水平的基础上。
所以板子要牢记。
还有就是,对于$Day1T2$这种一看就知道要$YY$很久的题,可以先跳过,把能拿的暴力分先用$namspace$封装起来。
这样至少有个保底分,想正解的时候也会信了踏实一点。
心态还是要稳啊!