模拟赛

发布时间 2023-07-11 16:03:12作者: zzxLLL

2023.7.10

T1

题面 在一个迷宫中有一个蛋糕。作为一个吃货,Luna 非常想吃到这块蛋糕。现在Luna 手里有这个迷宫的地图,该地图是一个r 行c 列的网格图,每个格子包含了下述4 种字符中的一种:

“#”表示这里是墙砖,不能通过;

“.”表示这里是空地,可以通过;

“S”表示Luna 的初始位置;

“C”表示蛋糕的位置。

Luna 只能在空地上行走,并且只能从一个格子走到与这个格子有公共边的相邻格子上。另外,整个地图的四周都是被墙砖所环绕的。

为了能更快吃到蛋糕,Luna 不知从哪里获得了一把次元枪,这把枪可以用来制造传送门。该枪的使用说明如下:

  1. 在任何时候,Luna 可以选择上下左右四个方向中的一个开一枪。当她向一个方向开枪之后,子弹会撞到这个方向上第一个遇到的墙砖,并在这个墙砖面向她的面上开启一个传送门。

  2. 由于能量限制,在同一时刻最多存在两个传送门。如果已经存在两个传送门,那么当Luna 再开枪时,其中一个传送门会被回收用于补充能量,回收哪一个由Luna 自己决定。当Luna 向某个传送门开枪时,会有一个新的传送门取代它,即在一块墙砖的一个面上同时只能存在一个传送门。

  3. 当存在两个传送门时,Luna 可以进入其中任意一个传送门,然后会从另外一个传送门走出。

由于Luna 枪法一流,所以开枪是不耗时的。Luna 从一个格子走到任意一个相邻格子的耗时为1,穿越传送门的耗时也为1。

现在Luna 把地图给了你,她想知道她吃到蛋糕的最少耗时是多少,你不忍心拒绝这样一位少女的请求,所以你绞尽脑汁也要把这个问题解决。

sol 最短路。

预处理出每个点走到上,下,左,右的最远点。

走到 \((x,y)\) 时,正常走可以走到 \((x+1,y),(x,y+1),(x-1,y),(x,y-1)\),如果用传送门,只需要走到最近的一面墙就能走到上下左右任意一面墙。跑 SPFA 即可。

#pragma GCC optimize("O2")
#include<queue>
#include<cstdio>
#include<utility>
#include<algorithm>
using namespace std;
const int M=1010,inf=1e9+7;

int n,m; int id(int x,int y){return (x-1)*m+y;}
int sx,sy,ex,ey;
char mp[M][M];

const int dx[]={-1,0,1,0},dy[]={0,-1,0,1};
typedef pair<int,int> PII;
PII to[M][M][4];
#define qwq make_pair
#define nino first
#define miku second
void pre(){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) if(mp[i][j]!='#'){
			to[i][j][0]=(mp[i-1][j]=='#')?qwq(i,j):to[i-1][j][0];
			to[i][j][1]=(mp[i][j-1]=='#')?qwq(i,j):to[i][j-1][1];
		}
	for(int i=n;i>=1;i--)
		for(int j=m;j>=1;j--) if(mp[i][j]!='#'){
			to[i][j][2]=(mp[i+1][j]=='#')?qwq(i,j):to[i+1][j][2];
			to[i][j][3]=(mp[i][j+1]=='#')?qwq(i,j):to[i][j+1][3];
		}
	/*
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			printf("to[%d][%d]=",i,j);
			for(int dir=0;dir<4;dir++)
				printf("(%d,%d),",to[i][j][dir].nino,to[i][j][dir].miku);
			putchar('\n');
		}*/
}

int dis[M][M]; queue<PII>q;
bool inq[M][M];
int dist(PII u,PII v){
	return abs(u.nino-v.nino)+abs(u.miku-v.miku);
}
#define D(x) dis[x.first][x.second]
#define Q(x) inq[x.first][x.second]
#define MP(x) mp[x.first][x.second]
void SPFA(){
	for(int i=0;i<M;i++)
		for(int j=0;j<M;j++) dis[i][j]=inf;
	dis[sx][sy]=0; q.push(qwq(sx,sy)),inq[sx][sy]=true;
	while(!q.empty()){
		PII u=q.front(); q.pop(),Q(u)=false;
		//printf("u=(%d,%d)\n",u.nino,u.miku);
		int mndis=inf;
		for(int dir=0;dir<4;dir++){
			PII v=to[u.nino][u.miku][dir];
			if(mndis>dist(u,v)) mndis=dist(u,v);
			
			v=qwq(u.nino+dx[dir],u.miku+dy[dir]);
			if(MP(v)=='#') continue;
			//printf("(%d,%d)->(%d,%d) dis=1\n",u.nino,u.miku,v.nino,v.miku);
			if(D(v)>D(u)+1){
				D(v)=D(u)+1;
				if(!Q(v)) q.push(v),Q(v)=true;
			}
		}
		for(int dir=0;dir<4;dir++){
			PII v=to[u.nino][u.miku][dir];
			//printf("(%d,%d)->(%d,%d) dis=%d\n",u.nino,u.miku,v.nino,v.miku,mndis+1);
			if(D(v)>D(u)+mndis+1){
				D(v)=D(u)+mndis+1;
				if(!Q(v)) Q(v)=true,q.push(v);
			}
		}
	}
	//for(int i=1;i<=n;i++,putchar('\n'))
	//	for(int j=1;j<=m;j++) printf("%d ",dis[i][j]);
}

int main(){
	freopen("portals.in","r",stdin);
	freopen("portals.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=0;i<=m+1;i++) mp[0][i]=mp[n+1][i]='#'; 
	for(int i=1;i<=n;i++) scanf(" %s",mp[i]+1),mp[i][0]=mp[i][m+1]='#';
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			if(mp[i][j]=='S') sx=i,sy=j;
			else if(mp[i][j]=='C') ex=i,ey=j;
	pre(),SPFA();
	printf("%d",dis[ex][ey]);
	return 0;
}

T2

题面 在2036 年的欧洲,老龄化现象已经渗透到了这个社会的各个角落,随之而来的是严重的健康问题。在欧洲有关部门的建议下,老年人被派遣去递送(也是给老年人的)信件。这项建议将在全欧洲得以实施。

有关部门将欧洲划分为若干个邮区。邮区是一个由双向街道和交叉路口组成的网络,每个区内都可以雇佣任意多的老年人。每天早晨,邮递员接到包裹并按一定路线投送。路线需要满足以下要求:

  1. 路线的起点和终点相同;

  2. 同一个路口不能经过两次(体谅老年人);

  3. 每条街道必须被恰好一条路线覆盖(体谅老年人)。

希望你帮助有关部门求出可行的路线分配方案。

sol

先找到一条不走任何重复边的最长路径。根据题意从 \(1\) 开始走最后一定回到 \(1\),而且一定每条边都恰好走一次。记录走到的节点顺序 \(ord\)

若有 \(ord_i = ord_j = u\) 说明 \([i,j)\) 形成一个环,输出并删除即可。

由题意,每个节点度数都是偶数,所以最后刚好删完。

用栈代替了 dfs 以防递归爆栈。

#include<cstdio>
#include<cstring>
const int M=5e5+10;

bool mark[M<<1];
int head[M],cur[M],cnte=1;
struct Edge{int to,next;}e[M<<1];
void add(int u,int v){
	e[++cnte]=(Edge){v,head[u]};
	head[u]=cnte;
}

int n,m;
int stk[M],top;
int ord[M<<1],top0;
bool vis[M];

int main(){
	freopen("postmen.in","r",stdin);
	freopen("postmen.out","w",stdout);
	memset(head,-1,sizeof head);
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;i++)
		scanf("%d%d",&u,&v),add(u,v),add(v,u);
	for(int i=1;i<=n;i++) cur[i]=head[i];
	
	stk[++top]=1;
	while(top>0){
		int u=stk[top],t=cur[stk[top]];
		if(t==-1){
			ord[++top0]=u; top--;
			continue;
		}
		cur[u]=e[cur[u]].next;
		if(mark[t]) continue;
		mark[t]=mark[t^1]=true;
		stk[++top]=e[t].to;
	}
	
	for(int i=1;i<=top0;i++){
		int cur=ord[i];
		if(vis[cur]){
			int tmp;
			do{
				tmp=stk[top--];
				printf("%d ",tmp),vis[tmp]=false;
			}while(tmp!=cur);
			putchar('\n');
		}
		stk[++top]=cur,vis[cur]=true;
	}
	return 0;
}

T3

题面

CF406E

\(m\) 个长度为 \(n\) 的01字符串,编号为 \(1\)\(m\),每个字符串中的位都按升序或降序排列。

\(H(a,b)\) 表示汉明距离,即两个相同长度的字符串对应位不同的位置的个数。计算所有由三个编号不同的串 \(a,b,c\) 组成的三元组中,\(H(a,b)+H(b,c)+H(c,a)\) 最大的三元组有多少个。

输入时将每个串用两个整数 \(s_i\in[0,1]\)\(f_i\in[0,n]\) 来表示,意为串的前 \(f_i\) 个位为 \(s_i\),后 \(n-f_i\) 个位为 \(1-s_i\)

sol

先考虑两个数的情况。

\[H(a,b) = \begin{cases} |f_a - f_b| & s_a = s_b \\ n - |f_a - f_b| & s_a \neq s_b \end{cases} \]

\(f\) 从小到大排序就可以脱掉绝对值。

将所有数排序去重,然后设 \((f_a, s_a)<(f_b, s_b)<(f_c, s_c)\)

  1. \((s_a, s_b, s_c)=(0, 0, 0) / (1, 1, 1)\)

\(H(a, b)+H(b, c)+H(c, a) = 2(f_c - f_a)\)

  1. \((s_a, s_b, s_c)=(0, 0, 1) / (1, 1, 0)\)

\(H(a, b)+H(b, c)+H(c, a) = 2(n + f_b - f_c)\)

  1. \((s_a, s_b, s_c)=(0, 1, 1) / (1, 0, 0)\)

\(H(a, b)+H(b, c)+H(c, a) = 2(n + f_a - f_b)\)

  1. \((s_a, s_b, s_c)=(1, 0, 1) / (0, 1, 0)\)

\(H(a, b)+H(b, c)+H(c, a) = 2n\)

到此并没有分类完,事实上还有 \((f_a, s_a)=(f_b, s_b)<(f_c, s_c)\)\((f_a, s_a)<(f_b, s_b)=(f_c, s_c)\) 的情况。其实只需要去重完特判这几种情况即可,因为若有三个或以上不同的数,那么这两种情况一定不优。

所以考虑枚举 \(b\),维护 \(s = 0/1\) 时 前缀/后缀 的 个数/最大值/最小值/最大值个数/最小值个数即可。

#include<cstdio>
#include<algorithm>
const int M=1e5+10,inf=1e9+7;

struct node{
	int f,s,cnt;
	bool operator<(const node&o)const{return f==o.f?s<o.s:f<o.f;}
}v[M];
int n,m,mx;

int pcnt[2][M],pmax[2][M],pmin[2][M],pcmx[2][M],pcmn[2][M];
int scnt[2][M],smax[2][M],smin[2][M],scmx[2][M],scmn[2][M];

int main(){
	freopen("hamming.in","r",stdin);
	freopen("hamming.out","w",stdout);
	for(int i=0;i<2;i++)
		for(int j=0;j<M;j++) pmin[i][j]=smax[i][j]=inf;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&v[i].s,&v[i].f),v[i].cnt=1;
	std::sort(v+1,v+1+m); int tot=0;
	for(int i=1;i<=m;i++)
		if(v[i].f!=v[i-1].f||v[i].s!=v[i-1].s||i==1) v[++tot]=v[i];
		else v[tot].cnt+=v[i].cnt;
	m=tot; //printf("m=%d\n",m);
	
	for(int i=1;i<=m;i++){
		int col=v[i].s;
		pcnt[col][i]=pcnt[col][i-1]+v[i].cnt,pcnt[col^1][i]=pcnt[col^1][i-1];
		pmax[col][i]=v[i].f,pmax[col^1][i]=pmax[col^1][i-1];
		pcmx[col][i]=v[i].cnt,pcmx[col^1][i]=pcmx[col^1][i-1];
		//pmin[col][i]=std::min(pmin[col][i-1],v[i].f),pmin[col^1][i]=pmin[col^1][i-1];
		if(pmin[col][i-1]==inf)
			pmin[col][i]=v[i].f,pcmn[col][i]=v[i].cnt;
		else
			pmin[col][i]=pmin[col][i-1],pcmn[col][i]=pcmn[col][i-1];
		pmin[col^1][i]=pmin[col^1][i-1],pcmn[col^1][i]=pcmn[col^1][i-1];
	}
	for(int i=m;i>=1;i--){
		int col=v[i].s;
		scnt[col][i]=scnt[col][i+1]+v[i].cnt,scnt[col^1][i]=scnt[col^1][i+1];
		smin[col][i]=v[i].f,smin[col^1][i]=smin[col^1][i+1];
		scmn[col][i]=v[i].cnt,scmn[col^1][i]=scmn[col^1][i+1];
		//smax[col][i]=std::max(smax[col][i+1],v[i].f),smax[col^1][i]=smax[col^1][i+1];
		if(smax[col][i+1]==inf)
			smax[col][i]=v[i].f,scmx[col][i]=v[i].cnt;
		else
			smax[col][i]=smax[col][i+1],scmx[col][i]=scmx[col][i+1];
		smax[col^1][i]=smax[col^1][i+1],scmx[col^1][i]=scmx[col^1][i+1];
	}
//	for(int i=1;i<=m;i++)
//		printf("p[%d][1]={%d,%d,%d,%d,%d}\n",i,pcnt[1][i],pmax[1][i],pcmx[1][i],pmin[1][i],pcmn[1][i]);
//	for(int i=1;i<=m;i++)
//		printf("s[%d][1]={%d,%d,%d,%d,%d}\n",i,scnt[1][i],smax[1][i],scmx[1][i],smin[1][i],scmn[1][i]);
	
	long long cnt=0; int ans=0;
	for(int b=1;b<=m;b++){
		int col=v[b].s;
		//(fa,sa)<(fb,sb)<(fc,sc)
		//(0,0,0),(1,1,1)
		if(pcnt[col][b-1]&&scnt[col][b+1]){
			//printf("case 1!\n");
			int tmp=2*(smax[col][b+1]-pmin[col][b-1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*pcmn[col][b-1]*v[b].cnt*scmx[col][b+1];
		}
		//(1,0,0),(0,1,1)
		if(pcnt[col^1][b-1]&&scnt[col][b+1]){
			//printf("case 2!\n");
			int tmp=2*(n-v[b].f+pmax[col^1][b-1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*pcmx[col^1][b-1]*v[b].cnt*scnt[col][b+1];
		}
		//(0,0,1)(1,1,0)
		if(pcnt[col][b-1]&&scnt[col^1][b+1]){
			//printf("case 3!\n");
			int tmp=2*(n+v[b].f-smin[col^1][b+1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans)  cnt+=1ll*pcnt[col][b-1]*v[b].cnt*scmn[col^1][b+1];
		}
		//(0,1,0),(1,0,1)
		if(pcnt[col^1][b-1]&&scnt[col^1][b+1]){
			//printf("case 4!\n");
			int tmp=2*n;
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*pcnt[col^1][b-1]*v[b].cnt*scnt[col^1][b+1];
		}
		
		//(fa,sa)<(fb,sb)=(fc,sc)
		//(0,0),(1,1)
		if(pcnt[col][b-1]){
			//printf("case 5!\n");
			int tmp=2*(v[b].f-pmin[col][b-1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*pcmn[col][b-1]*v[b].cnt*(v[b].cnt-1)/2;
		}
		//(1,0),(0,1)
		if(pcnt[col^1][b-1]){
			//printf("case 6!\n");
			int tmp=2*(n-v[b].f+pmax[col^1][b-1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*pcmx[col^1][b-1]*v[b].cnt*(v[b].cnt-1)/2; 
		}
		
		//(fa,sa)=(fb,sb)<(fc,sc)
		//(0,0),(1,1)
		if(scnt[col][b+1]){
			//printf("case 7!\n");
			int tmp=2*(smax[col][b+1]-v[b].f);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*scmx[col][b+1]*v[b].cnt*(v[b].cnt-1)/2;
		}
		//(1,0),(0,1)
		if(scnt[col^1][b+1]){
			//printf("case 8!\n");
			int tmp=2*(n+v[b].f-smin[col^1][b+1]);
			if(tmp>ans) ans=tmp,cnt=0;
			if(tmp==ans) cnt+=1ll*scmn[col^1][b+1]*v[b].cnt*(v[b].cnt-1)/2; 
		} 
		
		//(fa,sa)=(fb,sb)=(fc,sc)
		if("smr ak ioi"){
			//printf("case 9!\n");
			int tmp=0;
			if(tmp==ans) cnt+=1ll*v[b].cnt*(v[b].cnt-1)*(v[b].cnt-2)/6;
		}
	}
	printf("%lld",cnt);
	return 0;
}

T4

题面

CF420E

程序员不能总是整天坐着编程。有时站起来离开办公桌,休息一下,与同事闲聊,甚至玩一会,也是十分好的主意。F 公司的程序员就特别喜欢一种球类游戏。

让我们想象一个在笛卡尔坐标系平面上玩的游戏。玩家坐落在点(0; 0) 上,选择任意一个方向,扔出球。飞了一会儿的球在距离原点d 的地方撞击了平面,然后按照原来的方向继续飞。在第一次撞击之后,它继续飞且在距离原点

2d 的地方第二次撞击平面,接着诸如此类(按照选定的方向继续飞行,并且每隔d 单位距离撞击一次平面)。因为在F 公司的所有程序员都非常强壮,所以球可以飞到无穷远处。

平面上画有n 个圆。如果球撞击平面且撞在一个画在平面上的圆内(包括边界),那么玩家得一分。球可以一次击中多个圆,并且对于它们每一个得一分(如果球在移动过程中一共撞击了某一个圆x 次,那么玩家从中能得到x 分)。

计算玩家向任意方向扔一个球所能得到的最大分数。注意可能有实数坐标。

sol

很厉害的一道计算几何+差分。

对于一个圆看它能和多少个 \((0,0)\) 为圆心,半径为 \(k \cdots d\) 的圆相交/相切。

相交/相切会产生交点。可以发现,在一条射线旋转的过程中(对应扔球方向),只有经过这些交点,才会影响到答案。

所以考虑对角度差分。若交点为 \(A,B\) 两点,那么在 \(\angle AOB\) 之间答案加一。最后寻找最大答案即可。

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long double LD;
const double pii=acos(-1);
const int M=2e4+10,N=5e5+10;

int cnt; LD pos[N],neg[N]; 
int n,d,ans,tot;
#define sq(x) ((x)*(x))
#define ichika(x,y) make_pair(x,y)
#define nino first
#define miku second

int main(){
	scanf("%d%d",&n,&d);
	for(int i=1,x,y,r;i<=n;i++){
		scanf("%d%d%d",&x,&y,&r);
		LD alpha=atan2(y,x);
		if(alpha<0) alpha+=pii*2;
		
		LD dis=sqrt(sq(x)+sq(y));
		for(int k=ceil((dis-r)/d);k<=floor((dis+r)/d);k++){
			LD R=k*d;
			LD beta=acos((sq(dis)+sq(R)-sq(r))/(2*dis*R));
			LD v1=alpha-beta,v2=alpha+beta;
			if(v2>2*pii) v2-=2*pii,tot++;
			pos[++cnt]=v1,neg[cnt]=v2;
		}
	}
	ans=tot;
	sort(pos+1,pos+1+cnt),sort(neg+1,neg+1+cnt);
	for(int i=1,j=1;i<=cnt;){
		LD ang=pos[i];
		for(;i<=cnt&&pos[i]==ang;i++) tot++;
		for(;j<=cnt&&neg[j]<ang;j++)  tot--;
		ans=max(ans,tot);
	}
	printf("%d",ans);
	return 0;
}