NOIP2017

本蒟蒻表示对$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$。。。

虽然拿到了省一,但是还是有些不足吧。

最重要的几点:模板记牢;心态平和。

我就是抱着铁定二等的心态考出了一等。。。

不管怎么说吧,实力在那里,你能得的分,出题人抢不走;不能的得分,出题人也不会给你。

$NOIP2018$,加油!

原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9911101.html