题解:P16023 [ICPC 2021 NAC] Token Game
lailai0916 · · 题解
题意简述
棋盘上有两枚棋子。每次操作选择一枚棋子,将它的一个坐标减小,且不能越过或落在另一枚棋子上。求先手第一步能走到必败态的方案数。
解题思路
先构造一维辅助游戏。状态
每个非终止状态都能一步到达终止状态。因此,其 SG 值一定非零。
回到二维游戏,记横纵坐标差分别为
- 若
d_x+d_y=1 ,两枚棋子相邻。后手可以关于两枚棋子的中点模仿先手,因此这是必败态。 - 若
d_x\le1 或d_y\le1 ,但两枚棋子不相邻,先手可以直接走到相邻状态。这是必胜态。 - 若
d_x,d_y\ge2 ,留在该区域内的操作等价于两个一维辅助游戏的和。进入边界的操作只会到达上一类必胜态。SG 值为零的目标也不可能进入边界。否则一维 SG 值为零,另一维却非零,与异或为零矛盾。因此,当前局面必败当且仅当:
预处理所有
SG 表的预处理复杂度为
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=305;
const int mx=300;
int f[N][N];
bool vis[N*2];
bool lose(int x1,int y1,int x2,int y2)
{
int dx=abs(x1-x2),dy=abs(y1-y2);
if(dx+dy==1)return 1;
if(dx<=1||dy<=1)return 0;
return f[x1][x2]==f[y1][y2];
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
for(int i=1;i<=mx;i++)
{
for(int j=1;j<=mx;j++)
{
if(abs(i-j)<=1)continue;
memset(vis,0,sizeof vis);
for(int k=1;k<i;k++)vis[f[k][j]]=1;
for(int k=1;k<j;k++)vis[f[i][k]]=1;
while(vis[f[i][j]])f[i][j]++;
}
}
int q;
cin>>q;
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
int ans=0;
for(int i=1;i<x1;i++)
{
if(y1==y2&&i<=x2&&x2<x1)continue;
ans+=lose(i,y1,x2,y2);
}
for(int i=1;i<y1;i++)
{
if(x1==x2&&i<=y2&&y2<y1)continue;
ans+=lose(x1,i,x2,y2);
}
for(int i=1;i<x2;i++)
{
if(y1==y2&&i<=x1&&x1<x2)continue;
ans+=lose(x1,y1,i,y2);
}
for(int i=1;i<y2;i++)
{
if(x1==x2&&i<=y1&&y1<y2)continue;
ans+=lose(x1,y1,x2,i);
}
cout<<ans<<'\n';
}
return 0;
}