本蒟蒻表示对$NOIP2018$感到很虚。。。
于是把前几年的$NOIP$做一做。
第一个当然是我的处女战——$NOIP2017$!
$Day1$:
$T1$:小凯的疑惑
考场上手玩三组数据然后秒出结论的我。。。
答案就是$ab-a-b$。
证明?不存在的。。。
我们设$x=ak+bj(a<b,k\in[0,b-1])$。
显然当$j\geq0$时,$x$有解,舍去。
于是当$j=-1$时,$x$取到最大值$ak-b(k\in[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\n",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\n");
else printf("No\n");
}
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\n");
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\n");
else printf("%d\n",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)$有交面,一定满足:
$$2r\geq\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}$$
那个开方可能会有精度误差,于是:
$$(2r)^2\geq (x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2$$
即:
$$4r^2\geq (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\%$的数据,$n\leq1000$。。。
我竟然做完了$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\n");
else printf("No\n");
}
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\n");
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(n\log_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\n",s);
}
else{
s=splay[x].kth(y);
printf("%lld\n",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$。。。
虽然拿到了省一,但是还是有些不足吧。
最重要的几点:模板记牢;心态平和。
我就是抱着铁定二等的心态考出了一等。。。
不管怎么说吧,实力在那里,你能得的分,出题人抢不走;不能的得分,出题人也不会给你。