noip多校模拟25

Posted WindZR

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了noip多校模拟25相关的知识,希望对你有一定的参考价值。

考试过程:这次考试,觉得挺奇怪的,会的题一眼就会,不会的怎么想都不会。
按顺序开题,首先是T1,看到数据范围,觉得应该是\\(o(n)\\)出答案。然后基本上就是一眼出答案了。然后去厕所蹲了一会捋了捋思路,回来就切了。然后是T2,就很奇怪,怎么想也不会,就先弃了。T3,还是一眼出答案,我们可以枚举起点和循环节的长度,复杂度应该是\\(o(n^2\\times log)\\)的,但是考场上有个细节打错了,因为当时没有仔细算复杂度,就疯狂卡常,结果挂了\\(40pts\\).最后是T4,还是一眼出做法,最后优化一下就切了。

T1 石子合并

思路:显然,如果同时存在正数和负数,那么所有负数都会造成正贡献。那么我们对于只存在正数和负数的情况只需要拿出最大值减去最小值即可。
代码如下:

AC_code

#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=2e6+10;
const int INF=1e18;
int t,n,ja,jb,mina,maxa,minb,maxb;
int a[N],b[N];
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
#undef int
int main()
{
	freopen("stone.in","r",stdin);
	freopen("stone.out","w",stdout);
	#define int long long
	t=read();
	while(t--)
	{
		int x;
		ja=jb=0;
		maxa=maxb=-INF;
		mina=minb=INF;
		n=read();
		for(re i=1;i<=n;i++)
		{
			x=read();
			if(x>0)
				a[++ja]=x,mina=min(mina,x),maxa=max(maxa,x);
			else if(x<0)
				b[++jb]=x,minb=min(minb,x),maxb=max(maxb,x);
		}
		if(ja and jb)
		{
			int now=0;
			for(re i=1;i<=jb;i++) now=now+abs(b[i]);
			for(re i=1;i<=ja;i++) now=now+a[i];
			printf("%lld\\n",now);
		}		
		else if(ja)
		{
			if(ja==1) printf("%lld\\n",a[1]);
			else if(ja==2) printf("%lld\\n",abs(a[1]-a[2]));
			else
			{
				bool hn=0,hx=0;
				int now=maxa-mina;
				for(re i=1;i<=ja;i++)
				{
					if(a[i]==mina and hn==0) {hn=1;continue;}
					if(a[i]==maxa and hx==0) {hx=1;continue;}
					now=now+a[i];
				}
				printf("%lld\\n",now);
			}
		}
		else if(jb)
		{
			if(jb==1) printf("%lld\\n",b[1]);
			else if(jb==2) printf("%lld\\n",abs(b[1]-b[2]));
			else
			{
				bool hn=0,hx=0;
				int now=maxb-minb;
				for(re i=1;i<=jb;i++)
				{
					if(b[i]==minb and hn==0) {hn=1;continue;}
					if(b[i]==maxb and hx==0) {hx=1;continue;}
					now=now-b[i];
				}
				printf("%lld\\n",now);
			}
		}
		else printf("0\\n");
	}
	return 0;
}


T2 翻转游戏

思路:
最后去重即可。
代码如下:

AC_code

#include<bits/stdc++.h>
#define int long long
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=3e5+10;
const int INF=1e9+10;
struct node
{
	int x,y,x2,y2;
}cun[N],pre[N],suf[N];
int t,p,q,n,ans,base;
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
iv init()
{
	for(re i=0;i<=n+1;i++) pre[i]=suf[i]=(node){0,0,INF,INF};
	ans=base=0;
}
signed main()
{
	freopen("carpet.in","r",stdin);
	freopen("carpet.out","w",stdout);
	t=read();
	while(t--)
	{
		p=read(),q=read(),n=read();
		init();
		for(re i=1;i<=n;i++) cun[i]=(node){read(),read(),read()-1,read()-1};
		pre[1]=(node){cun[1].x,cun[1].y,cun[1].x2,cun[1].y2};
		suf[n]=(node){cun[n].x,cun[n].y,cun[n].x2,cun[n].y2};
		for(re i=2;i<=n;i++)
		{
			pre[i].x=max(pre[i-1].x,cun[i].x);
			pre[i].y=max(pre[i-1].y,cun[i].y);
			pre[i].x2=min(pre[i-1].x2,cun[i].x2);
			pre[i].y2=min(pre[i-1].y2,cun[i].y2);
		}
		for(re i=n-1;i;i--)
		{
			suf[i].x=max(suf[i+1].x,cun[i].x);
			suf[i].y=max(suf[i+1].y,cun[i].y);
			suf[i].x2=min(suf[i+1].x2,cun[i].x2);
			suf[i].y2=min(suf[i+1].y2,cun[i].y2);
		}
		if(pre[n].x2>=pre[n].x and pre[n].y2>=pre[n].y) ans=base=(pre[n].x2-pre[n].x+1)*(pre[n].y2-pre[n].y+1);
		for(re i=1;i<=n;i++)
		{
			int x=max(pre[i-1].x,suf[i+1].x);
			int y=max(pre[i-1].y,suf[i+1].y);
			int x2=min(pre[i-1].x2,suf[i+1].x2);
			int y2=min(pre[i-1].y2,suf[i+1].y2);
			if(x2>=x and y2>=y)
			{
				ans+=(x2-x+1)*(y2-y+1);
				if(pre[n].x>=x and pre[n].x2<=x2 and pre[n].y>=y and pre[n].y2<=y2) ans-=base;
			}
		}
		printf("%lld\\n",ans);
	}
	return 0;
}


T3 优美的旋律

思路:我们可以考虑枚举起点和循环节长度,然后计算循环节最长的循环次数,可以证明,这样的复杂度应该在\\(O(n^2\\times log)\\)左右。
代码如下:

AC_code

#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
typedef unsigned long long ull;
const int N=3100;
long long a,b,len;
long long ans;
char s[N];
ull base=1331;
ull ch[N],hs[N];
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
int main()
{
	freopen("melody.in","r",stdin);
	freopen("melody.out","w",stdout);
	a=read(),b=read();
	scanf("%s",s+1);
	len=strlen(s+1);
	ch[0]=1;
	for(re i=1;i<=len+5;i++) ch[i]=ch[i-1]*base;
	for(re i=1;i<=len;i++) hs[i]=hs[i-1]*base+(ull)s[i];
	for(re i=1;i<=len;i++)
	{
		int dis=len-i+1,mx=(dis/2);
		for(re j=1;j<=mx;j++)
		{
			ull my=hs[i+j-1]-hs[i-1]*ch[j];
			int up=len-j+1;
			long long now=0;
			for(re k=i+j;k<=up;k+=j)
			{
				if( my != (hs[k+j-1]-hs[k-1]*ch[j]) ) break;
				else ++now;
			}
			if(now) ++now,ans=max(ans,a*j+b*now);
		}
	}
	printf("%lld\\n",ans);
	return 0;
}


T4 基站建设

思路:因为要找两个主站和两个副站,那么我们可以枚举两个点作为主站,再枚举两个主站里儿子数较少的那个,计算最大值即可。
代码如下:

AC_code

#include<bits/stdc++.h>
#define ll long long
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=5e4+10;
int n,m;
ll ans;
ll val[N];
vector<int> v[N];
bitset<N> BS[N];
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
inline bool com(int x,int y) {return val[x]<val[y];}
int main()
{
	freopen("station.in","r",stdin);
	freopen("station.out","w",stdout);
	n=read(),m=read();
	for(re i=1;i<=n;i++) val[i]=read();
	int x,y;
	for(re i=1;i<=m;i++)
	{
		x=read(),y=read();
		v[x].push_back(y),v[y].push_back(x);
		BS[x][y]=1,BS[y][x]=1;
	}
	for(re i=1;i<=n;i++) if(v[i].size()) sort(v[i].begin(),v[i].end(),com);
	for(re i=1;i<=n;i++)
	{
		for(re j=0;j<v[i].size();j++)
		{
			int p=v[i][j];
			bitset<N> tmp=BS[i]&BS[p];
			if(tmp.count()<2) continue;
			ll mx1=0,mx2=0,pos=0;
			if(v[i].size()<=v[p].size())
			{
				for(re k=v[i].size()-1;k>=0;k--)
				{
					if(mx1 and mx2) break;
					if(BS[p][v[i][k]])
					{
						if(!mx1) mx1=val[v[i][k]];
						else if(!mx2) mx2=val[v[i][k]];
					}
				}
			}
			else
			{
				for(re k=v[p].size()-1;k>=0;k--)
				{
					if(mx1 and mx2) break;
					if(BS[i][v[p][k]])
					{
						if(!mx1) mx1=val[v[p][k]];
						else if(!mx2) mx1=val[v[p][k]];
					}
				}
			}
			ans=max(ans,(val[i]+1)*(val[p]+1)+mx1*mx2);
		}
	}
	printf("%lld\\n",ans);
	return 0;
}


noip多校模拟23

考试过程:开考先看题,觉得按顺序开题比较合适。首先是T1,刚开始没什么思路,而且比较困,神游了半个小时。然后突然有了思路,想到了回文串必定会有一个对称中心,那么我们可以从这个中心出发向两边扩展,设当前矩形的左上角和右下角分别是\\(x1,y1,x2,y2\\),那么可以扩展到
\\(x1-1,y1,x2+1,y2\\)
\\(x1-1,y1,x2,y2+1\\)
\\(x1,y1-1,x2+1,y2\\)
\\(x1,y1-1,x2,y2+1\\)
四个方向的矩形,直接转移即可,这样的复杂度是\\(o(n^4)\\),可以获得\\(50pts\\),然后我想了一会,觉得应该可以用类似与步数的方式优化到\\(o(n^3)\\),但是没什么具体的思路,就先打后面的题。
T2,阅读程序题,指针满天飞,属实看不懂,就弃掉了。
T3,想了一会想到一个\\(dp\\)思路,可以拿到\\(44pts\\),但是考场上打挂了,只拿了\\(12pts\\),错了好多小点,所以以后一定要测试点分治。
T4,没什么时间想了,就按照题意模拟,拿了\\(42pts\\)
总结:1.一定要打测试点分治
2.在看不懂题的情况下不要放弃,可以先看特殊性质,拿到白送的分。
3.如果有优化的思路,就要留出时间思考。

T1 回文

思路:暴力思路上面已经说过了,现在考虑如何优化。从对称中心出发,那么到达\\((1,1)\\)\\((n,m)\\)的步数是一样的,所以一定满足这样的柿子\\(x1=n-x2+1,y1=m-y2+1\\),那么一定满足\\(x1+y1+x2+y2==n+m+2\\),那么如果我们知道了\\(x1,y1,x2\\),那么\\(y2\\),可以通过计算得知,这样的复杂度就降到了\\(o(n^3)\\)
所以我们设\\(f_{i,j,k}\\)表示枚举的矩形的左上角为\\((i,j)\\),右下角的横坐标为\\(k\\)的方案数。转移是显然的。
最后考虑如何统计答案,我们找到所有\\(x+y-1==((n+m-2)/2)+1\\)的点,那么只需要\\(ans+=f_{x,y,x+1}+f_{x,y,x}\\)即可。
代码如下:

AC_code

#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int mo=993244853;
const int N=505;
int f[N][N][N];
char s[510][510];
int n,m,ans;
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
iv add(int &x,int y)
{
	x=x+y;
	if(x>=mo) x-=mo;
}
int main()
{
	freopen("palin.in","r",stdin);
	freopen("palin.out","w",stdout);
	n=read(),m=read();
	for(re i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
	}
	if(s[1][1]!=s[n][m]) {printf("0\\n");return 0;}
	f[1][1][n]=1;
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=m;j++)
		{
			for(re k=n;k>=i;k--)
			{
				int p=n+m-i-j-k+2;
				if(i==k and j==p) continue;
				if(p<j) continue;
				if((k-1>=i and j+1<=p) and s[i][j+1]==s[k-1][p]) add(f[i][j+1][k-1],f[i][j][k]);
				if((p-1>=j+1) and s[i][j+1]==s[k][p-1]) add(f[i][j+1][k],f[i][j][k]);
				if((k-1>=i+1) and s[i+1][j]==s[k-1][p]) add(f[i+1][j][k-1],f[i][j][k]);
				if((i+1<=k and p-1>=j)  and s[i+1][j]==s[k][p-1]) add(f[i+1][j][k],f[i][j][k]);
			}
		}
	}
	int len=(n+m-2)>>1;
	for(re i=1;i<=n;i++)
	{
		for(re j=1;j<=m;j++)
		{
			if((i+j-1)==len+1)
			{
				add(ans,f[i][j][i+1]);
				add(ans,f[i][j][i]);
			}
		}
	}
	printf("%d\\n",ans);
	return 0;
}


T2 快速排序

思路:经过计算机带师ICEY的讲解,终于看懂了题意。
首先,如果所有被排序数都非 nan,那么必然是按升序排列。
否则,我们考虑第一个数 \\(x\\)
如果 \\(x\\)\\(nan\\),那相当于把一个 \\(nan\\) 置于最前,然后排序其余的数。
否则,相当于把所有剩下的 \\(< x\\) 的数从小到大放在 \\(x\\) 之前,并放置 \\(x\\)
那么这个过程可以使用优先队列来实现。代码如下:

AC_code

#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=5e5+10;
struct node
{
	bool is;
	int val;
}cun[N];
int t,n,cnt;
priority_queue<int,vector<int>,greater<int> >Q;
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'n\')
		{
			f=0;
			ch=getchar();
			ch=getchar();
			ch=getchar();
			return -1;
		}
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-1);
}
int main()
{
	freopen("qsort.in","r",stdin);
	freopen("qsort.out","w",stdout);
	t=read();
	while(t--)
	{
		n=read();
		for(re i=1;i<=n+1;i++) cun[i]=(node){0,0};
		for(re i=1;i<=n;i++)
		{
			int x=read();
			if(x==-1) cun[i].is=1;
			else {cun[i].val=x,Q.push(x);}
		}
		for(re i=1;i<=n;++i)
		{
			if(cun[i].is==1) printf("nan ");
			else
			{
				while(Q.size() and Q.top()<cun[i].val) printf("%d ",Q.top()),Q.pop();
				if(Q.size() and Q.top()==cun[i].val) printf("%d ",Q.top()),Q.pop();
			}
		}
		printf("\\n");
	}
	return 0;
}


T3 混乱邪恶

思路:目前只会打部分分,说一下部分分的思路:我们设\\(f_{i,j0/1}\\)表示前\\(i\\)个数能否凑出和为\\(j\\),如果可以,那么最后一个数的位置是什么。那么我们就可以暴力转移了。考虑优化,我们显然可以滚掉一维,但是要注意,如果之前已经凑出过当前位置的数了,那么就不需要更新了,否则因为我利用的是之前的数进行的更新,当我修改之后,当我统计答案的时候就不对了。
代码如下:

AC_code

#include<bits/stdc++.h>
#define re register int
#define ii inline int
#define iv inline void
using namespace std;
const int N=1e6+10;
int n,m,sum;
int a[N],ans[N];
int f[N][2],pre[N][2];
bool flag;
ii read()
{
	int x=0; char ch=getchar(); bool f=1;
	while(ch<\'0\' or ch>\'9\')
	{
		if(ch==\'-\') f=0;
		ch=getchar();
	}
	while(ch>=\'0\' and ch<=\'9\')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return f?x:(-x);
}
signed main()
{
	freopen("chaoticevil.in","r",stdin);
	freopen("chaoticevil.out","w",stdout);
	n=read(),m=read();
	for(re i=1;i<=n;i++) a[i]=read(),sum+=a[i];
	sum/=2;
	for(re i=1;i<=n;i++) ans[i]=-1;
	f[a[1]][0]=f[a[1]][1]=1;
	for(re i=2;i<=n;i++)
	{
		for(re j=sum-a[i];j;j--)
		{
			if( (!f[j+a[i]][0]) and (f[j][0]) )
			{
				f[j+a[i]][0]=1;
				f[j+a[i]][1]=i;
				pre[j+a[i]][0]=f[j][1];	
				pre[j+a[i]][1]=j;
			}
		}
		if(!f[a[i]][0])
		{
			f[a[i]][0]=1,f[a[i]][1]=i;
			pre[a[i]][0]=pre[a[i]][1]=0;
		}
		if(f[sum][0]) {flag=1;break;}
	}
	if(flag)
	{
		printf("NP-Hard solved\\n");
		ans[f[sum][1]]=1;
		while(sum and pre[sum][0])
		{
			ans[pre[sum][0]]=1;
			sum=pre[sum][1];
		}
		for(re i=1;i<=n;i++)
			printf("%d ",ans[i]);
		printf("\\n");
	}
	else printf("Chaotic evil\\n");
	return 0;
}


T4 校门外歪脖树上的鸽子

咕了。

以上是关于noip多校模拟25的主要内容,如果未能解决你的问题,请参考以下文章

noip多校模拟23

2017 10.25 NOIP模拟赛

noip模拟70

noip多校21

2017.11.25NOIP提高组模拟赛A组

2019.5.25 Noip模拟测试2