本蒟蒻表示对$NOIP2018$感到很虚。。。
于是把前几年的$NOIP$做一做。
第一个当然是我的处女战——$NOIP2017$!
$Day1$:
$T1$:小凯的疑惑
考场上手玩三组数据然后秒出结论的我。。。
答案就是$ab-a-b$。
证明?不存在的。。。
我们设$x=ak+bj(a<b,kin[0,b-1])$。
显然当$jgeq0$时,$x$有解,舍去。
于是当$j=-1$时,$x$取到最大值$ak-b(kin[0,b-1])$。
显然,当$k=b-1$时,$x$取到最大值$a(b-1)-b$。
于是答案就是$ab-a-b$。
记得开$long long$。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; long long a,b; int main(){ cin>>a>>b; printf("%lld ",a*b-a-b); return 0; }
$T2$:时间复杂度
考场上暴力写挂的我。。。
一道巨大的字符转模拟题。
其实只要按照正常的模拟来做就好。
注意一些很容易想到的细节。
我就是因为读数字只读了一位然后暴力写挂的。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define MAXN 10010 #define MAX 9999999 using namespace std; int n,o; struct node{ int f,i,x,y,last; }a[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 change(char* x){ int date=0,l=strlen(x); for(int i=0;i<l;i++) if(x[i]>='0'&&x[i]<='9') date=date*10+x[i]-'0'; return date; } void work(){ int fzd=0,ans=0; for(int i=1;i<=n;i++){ if(a[i].f==0){ ans=max(fzd,ans); if(a[a[i].last].x<=100&&a[a[i].last].y==MAX)fzd--; continue; } if(a[i].x<=100&&a[i].y==MAX)fzd++; else if((a[i].x==MAX&&a[i].y<=100)||(a[i].x>a[i].y)){ int g=1,h=0; while(++i<=n){ if(a[i].f==1)g++; else h++; if(g==h&&a[i].f==0)break; } } } if(ans==o)printf("Yes "); else printf("No "); } void init(){ int b=0,h=0,s[MAXN*10]; char d[20]; bool flag=false,t[30]; memset(a,0,sizeof(a)); memset(t,false,sizeof(t)); scanf("%d",&n);scanf("%s",d); if(d[2]=='1')o=0; else o=change(d); for(int i=1;i<=n;i++){ scanf("%s",d); if(d[0]=='F'){ a[i].f=1;b++; scanf("%s",d);a[i].i=d[0]-'a'; scanf("%s",d);a[i].x=(d[0]=='n'?MAX:(change(d))); scanf("%s",d);a[i].y=(d[0]=='n'?MAX:(change(d))); if(t[a[i].i])flag=true; t[a[i].i]=true; s[++h]=i; } else{ a[i].f=0;b--; int v=s[h]; h--; t[a[v].i]=false; a[i].last=v; } if(b<0)flag=true; } if(b!=0||flag){ printf("ERR "); return; } work(); } int main(){ int t=read(); while(t--)init(); return 0; }
$T3$:逛公园
考场上连暴力都不会写的我。。。
首先你可以想到,$Day1$的$DP$去哪里了。。。
于是这题就跟$DP$脱不了干系了(雾。。。
建议先去做这题:
P1608 路径统计
然后就有$30$的部分分了。。。
然后开始想$DP$:
首先一个$spfa$不解释。。。
设$f[u][j]$为到达$u$节点时路径长与最短路径$d$的差为$j$(即:偏移量)。
于是:
我们枚举最后总的偏移量,然后记忆化搜索就行了。
但是这样真的对么?
你是不是忘了考虑它有的点不能到$n$(巨坑。。。),所以在算偏移量的时候依据从起点开始的最短路是不对的。
那这怎么办?
我们把边反过来,从$n$开始最短路,按照这个最短路来算偏移量就行了。
正确性应该是对的,在下不证了。。。
那,$0$环怎么办?
我们只要看$(x,v)$这个状态是否被访问了两次就好了嘛。。。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<queue> #include<cstring> #define MAXN 100010 #define MAXK 55 #define MAX 999999999 using namespace std; int n,m,c=1,d=1,k,p,ans; int ahead[MAXN],bhead[MAXN],path[MAXN],f[MAXN][MAXK]; bool flag,vis[MAXN],g[MAXN][MAXK]; struct node{ int next,to,w; }a[MAXN<<1],b[MAXN<<1]; 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 add(int u,int v,int w){ a[c].to=v;a[c].w=w; a[c].next=ahead[u]; ahead[u]=c++; b[d].to=u;b[d].w=w; b[d].next=bhead[v]; bhead[v]=d++; } inline int relax(int u,int v,int w){ if(path[v]>path[u]+w){ path[v]=path[u]+w; return 1; } return 0; } void spfa(){ int u,v; queue<int> q; for(int i=1;i<=n;i++){path[i]=MAX;vis[i]=false;} path[n]=0; vis[n]=true; q.push(n); while(!q.empty()){ u=q.front(); q.pop(); vis[u]=false; for(int i=bhead[u];i;i=b[i].next){ v=b[i].to; if(relax(u,v,b[i].w)&&!vis[v]){ vis[v]=true; q.push(v); } } } } int dfs(int dis,int x){ if(g[x][dis]){ flag=true; return 0; } if(f[x][dis]!=-1)return f[x][dis]; g[x][dis]=true; int s=0; for(int i=ahead[x];i;i=a[i].next){ int v=a[i].to,t=dis-(path[v]+a[i].w-path[x]); if(t>k||t<0)continue; int x=dfs(t,v); s=(s+x)%p; if(flag)return 0; } g[x][dis]=false; if(x==n&&dis==0)s++; return f[x][dis]=s; } void work(){ spfa(); for(int i=0;i<=k;i++){ memset(g,false,sizeof(g)); int x=dfs(i,1); ans=(ans+x)%p; } if(flag)printf("-1 "); else printf("%d ",ans); } void init(){ int u,v,w; c=d=1; ans=0; flag=false; memset(ahead,0,sizeof(ahead)); memset(bhead,0,sizeof(bhead)); memset(f,-1,sizeof(f)); n=read();m=read();k=read();p=read(); for(int i=1;i<=m;i++){ u=read();v=read();w=read(); add(u,v,w); } work(); } int main(){ int t=read(); while(t--)init(); return 0; }
总结:
$Day1$应该按我那时的水平,$200$是肯定有的。
但是只拿到了$140=100+40+0$很可惜。。。
被我市的一群初中生吊打。。。
其实只要暴力打好,应该没有什么问题。
还有就是,心态要稳。
$Day2$:
$T1$:奶酪
考场上一拿道题——啥啥啥?三维计算几何???我连二维都不会啊。。。
然后认真审题——毕竟这是$NOIPDay2T1$,出题人是不敢出得太毒的。。。
赶紧想暴力。。。
首先发现,如果两个球$(x_1,y_1,z_1),(x_2,y_2,z_2)$有交面,一定满足:
$$2rgeqsqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}$$
那个开方可能会有精度误差,于是:
$$(2r)^2geq (x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2$$
即:
$$4r^2geq (x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2$$
然后我们把所有有交面的球看成一个一个的点,连边。
再把$z=0,z=h$当成源点和汇点。
我们发现如果有解,源点与汇点一定存在一条路径。
然后就可以乱搞了。
本蒟蒻当时脑子一热,一发$SPFA$上去。。。
各路大神的做法千奇百怪:并查集、$BFS$、$SPFA$、$Dijkstra$。。。
然后。。。这个复杂度是$O(n^2)$的吧。。。
看看我拿了多少分——对于$100\%$的数据,$nleq1000$。。。
我竟然做完了$T1$!!!
当时激动得手抖——离省一又近了一大步!
注意:记得开$long long$。
但是我当时估测完数据之后发现——极端数据要开$unsigned long long$!
吓得我赶紧改。。。
但是出题人并没有卡$long long$。。。
于是就这样了。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define MAXN 1010 #define MAX 999999999 using namespace std; int n,h,s,t,c; unsigned long long r; int head[MAXN],path[MAXN]; bool vis[MAXN]; struct node1{ long long x,y,z; }b[MAXN]; struct node2{ int next,to,w; }a[MAXN*MAXN<<1]; 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 int relax(int u,int v,int w){ if(path[v]>path[u]+w){ path[v]=path[u]+w; return 1; } return 0; } void add(int x,int y){ a[c].to=y;a[c].w=1; a[c].next=head[x]; head[x]=c++; a[c].to=x;a[c].w=1; a[c].next=head[y]; head[y]=c++; } bool check(int i,int j){ unsigned long long ss=(unsigned long long)(b[i].x-b[j].x)*(b[i].x-b[j].x)+(b[i].y-b[j].y)*(b[i].y-b[j].y)+(b[i].z-b[j].z)*(b[i].z-b[j].z); if(ss>r*r*4)return false; return true; } bool spfa(){ int u,v; queue<int> q; for(int i=0;i<=n+1;i++){path[i]=MAX;vis[i]=false;} path[s]=0; vis[s]=true; q.push(s); while(!q.empty()){ u=q.front(); q.pop(); vis[u]=false; for(int i=head[u];i;i=a[i].next){ v=a[i].to; if(relax(u,v,a[i].w)&&!vis[v]){ vis[v]=true; q.push(v); } } } if(path[t]==MAX)return false; return true; } void work(){ if(spfa())printf("Yes "); else printf("No "); } void init(){ bool f=false,g=false; n=read();h=read();r=read(); c=1;s=0;t=n+1; memset(head,0,sizeof(head)); for(int i=1;i<=n;i++){ b[i].x=read();b[i].y=read();b[i].z=read(); if(abs(b[i].z)<=r){ g=true; add(i,s); } if(abs(b[i].z-h)<=r){ f=true; add(i,t); } for(int j=1;j<i;j++)if(check(i,j))add(i,j); } if((!f)||(!g)){ printf("No "); return; } work(); } int main(){ int cases=read(); while(cases--)init(); return 0; }
$T2$:宝藏
本蒟蒻耗费缀长的一题。。。
一开始像暴力$DFS$,然后发现并不会写。。。
前几天学了网络流$Dinic$,里面有个有个$BFS$分层图。
我一想——好像对于$40\%$的数据是可以的!
然后开始码码码。。。
我觉得我现在也不一定能写得出来。。。
各种写搓+各种写挂。。。
$1h$后——我$^{TM}$终于过了大样例啊!!!
于是$40$分就到手了。。。
($PS$:后来才发现。写个$DFS$再加个剪枝能拿$70$。。。然而并不会写。。。尴尬)
正解的话。。。
我也懒得复制了——戳这里!
$T3$:列队
考场上二话不说写暴力的我。。。
其实如果再想一想的话,$50$的平衡树还是能想出来的。。。
据说正解是树状数组。。。但是我不会啊。。。
我们观察每一行除了最后一个数之外的数在操作中是如何变化的。
如果出队在$(x,y)$,那么第$x$行(除了最后一个数)会弹出第$y$个数,它后面的数依次左移,再在最后插入一个数(就是最后一列当前的第x个数)。
然后,对于最后一列,我们要弹出第$x$个数(插入到第$x$行),再在最后插入一个数(就是刚出队的那个)。
所以,我们无论是对于行还是列,都要执行两种操作:
第一,弹出第$k$个数;
第二,在尾部插入一个数。
这可以用$Splay$来实现。
但是这样的话空间复杂度是$O(nm)$。。。
我们发现有些人从始至终都是左右相邻的,这些人对应的$Splay$节点可以合并。
于是我们在维护$Splay$的时候,所有连续的数的区间我们用一个节点维护,记录这个节点表示的区间的左右端点;
如果我们发现要弹出的数在某个节点内部,我们就把它拆开,拆成$3$个节点(其中中间那个节点只有一个数,就是我们要的那个数)。
拆点的过程可以直接跑$Splay$上的$insert$,删除中间那个节点就是$Splay$上的删除。
总时间复杂度为$O(nlog_2n)$。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 3000010 using namespace std; long long n,m,q; int c=1; struct tree{ int son[2]; int f; long long s,l,r; }a[MAXN]; struct line{ int root; line(){root=0;} inline void pushup(int rt){ if(!rt)return; a[rt].s=a[rt].r-a[rt].l+1; if(a[rt].son[0])a[rt].s+=a[a[rt].son[0]].s; if(a[rt].son[1])a[rt].s+=a[a[rt].son[1]].s; } inline int newnode(long long l,long long r){ int rt=c++; a[rt].f=a[rt].son[0]=a[rt].son[1]=0; a[rt].l=l;a[rt].r=r;a[rt].s=r-l+1; return rt; } inline void turn(int rt,int &f){ int x=a[rt].f,y=a[x].f,k=a[x].son[1]==rt; if(x==f)f=rt; else a[y].son[a[y].son[1]==x]=rt; a[rt].f=y;a[x].f=rt;a[a[rt].son[k^1]].f=x; a[x].son[k]=a[rt].son[k^1];a[rt].son[k^1]=x; pushup(x);pushup(rt); } void splay(int rt,int &f){ while(rt^f){ int x=a[rt].f,y=a[x].f; if(a[rt].f^f)turn((a[x].son[1]==rt)^(a[y].son[1]==x)?rt:x,f); turn(rt,f); } pushup(rt); } int find(int rt){ while(a[rt].son[1])rt=a[rt].son[1]; return rt; } inline void insert(long long x){ int rt=newnode(x,x),last=find(root); a[last].son[1]=rt;a[rt].f=last; splay(rt,root); } inline void buildtree(long long l,long long r){root=newnode(l,r);} int next(int rt){ rt=a[rt].son[1]; while(a[rt].son[0])rt=a[rt].son[0]; return rt; } inline long long split(int rt,long long x){ splay(rt,root); x+=a[rt].l-1; int y=newnode(x+1,a[rt].r); a[rt].r=x-1; if(!a[rt].son[1]){ a[rt].son[1]=y; a[y].f=rt; } else{ int k=next(rt); a[k].son[0]=y;a[y].f=k; splay(y,root); } return x; } long long kth(long long x){ int rt=root; while(1){ int y=a[rt].son[0]; if(x<=a[y].s)rt=y; else{ x-=a[y].s; long long flag=a[rt].r-a[rt].l+1; if(x<=flag){ if(x==1){ long long ans=a[rt].l; a[rt].l++; splay(rt,root); pushup(rt); return ans; } else if(x==flag){ long long ans=a[rt].r; a[rt].r--; splay(rt,root); pushup(rt); return ans; } else return split(rt,x); } x-=a[rt].r-a[rt].l+1; rt=a[rt].son[1]; } } } }splay[MAXN]; inline long long read(){ long long 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 init(){ n=read();m=read();q=read(); for(int i=1;i<=n;i++)splay[i].buildtree(m*(i-1)+1,m*i-1); splay[0].buildtree(m,m); for(int i=2;i<=n;i++)splay[0].insert(m*i); } void work(){ int x,y; long long s; while(q--){ x=read();y=read(); if(y==m){ s=splay[0].kth(x); printf("%lld ",s); } else{ s=splay[x].kth(y); printf("%lld ",s); splay[x].insert(splay[0].kth(x)); } splay[0].insert(s); } } int main(){ init(); work(); return 0; }
总结:
$Day2$算是拿到了预计的分数:$170=100+40+30$
但是,怎么说呢,能更好一点吧。
毕竟有些算法是考完之后听其他大佬一说然后恍然大悟。
然后又被初中的大佬吊打了。。。
$NOIP2017$总结:
其实如果发挥的好的话,应该是$370+$的。
但是因为码力不足等种种原因,最终只有$310$。。。
那群初二的巨佬一个个都去了$WC$。。。
虽然拿到了省一,但是还是有些不足吧。
最重要的几点:模板记牢;心态平和。
我就是抱着铁定二等的心态考出了一等。。。
不管怎么说吧,实力在那里,你能得的分,出题人抢不走;不能的得分,出题人也不会给你。