2023NOIP A层联测17 游戏

题目大意

有一个n×mn\times mn×m的网格图,开始时有三种格子,分别为障碍,棋子和空格。

AAA和小BBB在这个网格图上玩游戏,双方轮流行动,小AAA先行动。每次行动中,当前玩家选择一枚棋子移动,设棋子所在的格子为(r,c)(r,c)(r,c),则移动规则如下:

  • 可将格子移到(r+1,c)(r+1,c)(r+1,c)(如果r+1≤nr+1\leq nr+1n)、(r,c−1)(r,c-1)(r,c1)(如果c>1c>1c>1)、(r,c+1)(r,c+1)(r,c+1)(如果c<mc<mc<m)。特殊地,可以从(r,1)(r,1)(r,1)移到(r,m)(r,m)(r,m),也可以从(r,m)(r,m)(r,m)移到(r,1)(r,1)(r,1),也就是可以将每一行看作一个环。
  • 棋子不能被移动到包含障碍的格子
  • 一个格子可以包含多个棋子
  • 棋子不能被移动到它曾经到过的格子(所有棋子各不相同,也就是到过的格子是对每个棋子分别记录的)

无法行动的玩家算负。

求在双方都采用最优策略的情况下,最终谁会获胜。

TTT组数据。

1≤n,m≤1000,1≤∑n,∑m≤40001\leq n,m\leq 1000,1\leq \sum n,\sum m\leq 40001n,m1000,1n,m4000

时间限制2000ms2000ms2000ms,空间限制512MB512MB512MB


题解

由于每个格子上可以有多个棋子,且每个棋子走过的路径单独记录,所以每个棋子是独立的。因此,我们可以先计算出每个位置有棋子时的SGSGSG值,最后再将其异或在起来,即可得到总游戏的SGSGSG值。

我们考虑DPDPDP,设sg[x][y]sg[x][y]sg[x][y]表示当前棋子一开始在(x,y)(x,y)(x,y)时的SGSGSG值,然后从下往上处理。我们分两种情况考虑。

当前行有至少一个包含障碍的格子

在这种情况下,每行都被障碍分成若干段。那么,对于走到这一行的棋子,它的状态只会有三种:

  • 刚从上面走下来,此时可以往左右两边走
  • 从右边走过来,只能往左边走
  • 从左边走过来,只能往右边走

假设当前行为第ttt行,设f[0/1][i]f[0/1][i]f[0/1][i]表示当前是从左边或右边走过来,走到当前行的位置iii时的SGSGSG值。我们分别处理从左往右走的fff值和从右往左走的fff值,对于每个位置iii,我们用sg[t+1][i]sg[t+1][i]sg[t+1][i]f[0][i−1]f[0][i-1]f[0][i1]f[1][i+1]f[1][i+1]f[1][i+1]三个数求mexmexmex来更新sg[t][i]sg[t][i]sg[t][i]

当前行没有包含障碍的格子

在这种情况下,当一个棋子从上一行来到这一行时,假设来到了(x,y)(x,y)(x,y),若选择往左走,来到(x,y−1)(x,y-1)(x,y1),则(x,y)(x,y)(x,y)就相当于变成了一个包含障碍的格子,这就转化为了包含障碍格子的情况。

我们考虑如何计算SGSGSG值。当从(x,y)(x,y)(x,y)走到(x,y−1)(x,y-1)(x,y1)时,只能往左或往下走,也就是说我们要用(x+1,y−1)(x+1,y-1)(x+1,y1)SGSGSG值和(x,y−2)(x,y-2)(x,y2)SGSGSG值来更新(x,y−1)(x,y-1)(x,y1)SGSGSG值。而(x+1,y−1)(x+1,y-1)(x+1,y1)SGSGSG值在之前已经求出,所以我们需要求的就是(x,y−2)(x,y-2)(x,y2)SGSGSG值。以此类推,我们一路向左推,最终要求的就是(x,y+1)(x,y+1)(x,y+1)SGSGSG值。此时只能往下走,可以直接求出SGSGSG值,再一路回推。

这样的话,求每个位置的SGSGSG值的时间复杂度都是O(m)O(m)O(m)的。我们考虑优化。

我们可以注意到,每个位置的SGSGSG值是由三个数求mexmexmex得来,也就是说SGSGSG值只会取0,1,2,30,1,2,30,1,2,3四个数。

假设当前行为第ttt行,设g[0/1/2/3][j][i]g[0/1/2/3][j][i]g[0/1/2/3][j][i]的意义如下:

  • g[0][j][i]g[0][j][i]g[0][j][i]表示当位置(t,1)(t,1)(t,1)SGSGSG值为jjj时,一路往右回推到iii(也就是从棋子从iii往左走到111),此时位置(t,i)(t,i)(t,i)SGSGSG
  • g[1][j][i]g[1][j][i]g[1][j][i]表示当位置(t,m)(t,m)(t,m)SGSGSG值为jjj时,一路往左回推到iii(也就是从棋子从iii往右走到mmm),此时位置(t,i)(t,i)(t,i)SGSGSG
  • g[2][j][i]g[2][j][i]g[2][j][i]表示位置(t,i)(t,i)(t,i)SGSGSGjjj时,iii是从(t,1)(t,1)(t,1)向左回推过来的,此时位置(t,1)(t,1)(t,1)的值
  • g[3][j][i]g[3][j][i]g[3][j][i]表示位置(t,i)(t,i)(t,i)SGSGSGjjj时,iii是从(t,m)(t,m)(t,m)向右回推过来的,此时位置(t,m)(t,m)(t,m)的值

那么,我们分别将g[0][j][i]g[0][j][i]g[0][j][i]g[1][j][i]g[1][j][i]g[1][j][i]g[2][j][i]g[2][j][i]g[2][j][i]g[3][j][i]g[3][j][i]g[3][j][i]求出来,然后用他们来O(1)O(1)O(1)更新每个点的SGSGSG值即可。

可以参考代码方便理解。

两种情况处理每行的时间复杂度都为O(m)O(m)O(m),所以总时间复杂度为O(nm)O(nm)O(nm)


code

#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int T,n,m,ans,sg[N+5][N+5],f[2][N+5],g[4][4][N+5];
char s[2*N+5][2*N+5];
int mex(int v1,int v2,int v3){
	for(int i=0;i<=3;i++){
		if(i!=v1&&i!=v2&&i!=v3) return i;
	}
}
int gt(int i){
	return (i-1)%m+1;
}
void solve1(int t){
	memset(f,0,sizeof(f));
	for(int i=1;i<=m;i++) s[t][i+m]=s[t][i];
	int fst=1;
	while(s[t][fst]!='#') ++fst;
	for(int l=fst,r;l<m+fst;l=r){
		r=l+1;
		while(r<m+fst&&s[t][r]!='#') ++r;
		if(r==l+1) continue;
		f[0][l+1]=mex(sg[t+1][gt(l+1)],-1,-1);
		for(int i=l+2;i<r;i++) f[0][i]=mex(f[0][i-1],sg[t+1][gt(i)],-1);
		f[1][r-1]=mex(sg[t+1][gt(r-1)],-1,-1);
		for(int i=r-2;i>l;i--) f[1][i]=mex(f[1][i+1],sg[t+1][gt(i)],-1);
		for(int i=l+1;i<=r-1;i++){
			int v1=-1,v2=-1;
			if(i>l+1) v1=f[0][i-1];
			if(i<r-1) v2=f[1][i+1];
			sg[t][gt(i)]=mex(v1,v2,sg[t+1][gt(i)]);
		}
	}
}
void solve2(int t){
	if(m==1){
		sg[t][1]=mex(sg[t+1][1],-1,-1);
		return;
	}
	memset(g,0,sizeof(g));
	for(int j=0;j<=3;j++){
		g[0][j][1]=j;
		g[1][j][m]=j;
		g[2][j][1]=j;
		g[3][j][m]=j;
	}
	for(int i=2;i<=m;i++)
	for(int j=0;j<=3;j++) g[0][j][i]=mex(g[0][j][i-1],sg[t+1][i],-1);
	for(int i=m-1;i>=1;i--)
	for(int j=0;j<=3;j++) g[1][j][i]=mex(g[1][j][i+1],sg[t+1][i],-1);
	for(int i=2;i<=m;i++)
	for(int j=0;j<=3;j++) g[2][j][i]=g[2][mex(j,sg[t+1][i-1],-1)][i-1];
	for(int i=m-1;i>=1;i--)
	for(int j=0;j<=3;j++) g[3][j][i]=g[3][mex(j,sg[t+1][i+1],-1)][i+1];
	for(int i=1;i<=m;i++){
		int v1=-1,v2=-1,now;
		int nxt=gt(i+1),ed=g[3][mex(sg[t+1][nxt],-1,-1)][nxt];
		if(i==1) v1=ed;
		else{
			if(i==m) now=mex(sg[t+1][1],-1,-1);
			else now=mex(sg[t+1][1],ed,-1);
			v1=g[0][now][i-1];
		}
		int lst=gt(i-1+m),bg=g[2][mex(sg[t+1][lst],-1,-1)][lst];
		if(i==m) v2=bg;
		else{
			if(i==1) now=mex(sg[t+1][m],-1,-1);
			else now=mex(sg[t+1][m],bg,-1);
			v2=g[1][now][i+1];
		}
		sg[t][i]=mex(v1,v2,sg[t+1][i]);
	}
}
int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		memset(sg,-1,sizeof(sg));
		for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
		for(int i=n;i>=1;i--){
			int fl=0;
			for(int j=1;j<=m;j++){
				if(s[i][j]=='#'){
					fl=1;break;
				}
			}
			if(fl) solve1(i);
			else solve2(i);
		}
		ans=0;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++){
				if(s[i][j]=='B') ans^=sg[i][j];
			}
		}
		if(ans) printf("A\n");
		else printf("B\n");
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值