题解:P16023 [ICPC 2021 NAC] Token Game
lailai0916 · · 题解
题意简述
棋盘上有两枚棋子。每次选择一枚棋子,将它的一个坐标严格减小,且不能越过或落到另一枚棋子上。求第一步能走到必败态的合法操作数量。
解题思路
先考虑一维辅助游戏。状态
按
回到二维游戏,令:
首先分类讨论靠近边界的局面。
- 若
d_x+d_y=1 ,两枚棋子同行或同列且相邻。后手可以沿同一方向模仿先手,使局面重新相邻,故这是必败态。 - 若
d_x\le1 或d_y\le1 ,但两枚棋子并不相邻,先手总能把较大的某个坐标减小,直接得到相邻局面,故这是必胜态。
剩下
若一步走到边界,但没有相邻,则目标已经由上一类证明为必胜态。另一方面,异或为零的目标不可能只有某一维进入终止状态,因为另一维的 SG 值非零。因此当前局面必败当且仅当:
直接枚举每组询问的所有移动已经可以通过,但还可以预处理目标坐标,将询问降为对数时间。
考虑把某一坐标从
记
对每个
四种移动分别调用一次相同的计数函数。设棋盘边长为
正确性证明
相邻局面中,后手始终可以把先手移动后的另一枚棋子移到对应相邻位置。这个应对不会越过棋子,并且每两步都严格减小坐标,所以相邻局面为必败态。任何满足某一维差至多为
在两维差均至少为
calc 首先构造所有不越界、不穿过另一枚棋子的目标区间。另一维差为 calc 之和就是第一步必胜操作数。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=305;
const int mx=300;
int f[N][N];
bool vis[N*2];
vector<int> pos[N][N*2];
int calc(int x,int y,int a,int b)
{
int l=1,r=x-1;
if(a==b&&y<x)l=y+1;
if(l>r)return 0;
int d=abs(a-b);
if(d==0)return (l<=y-1&&y-1<=r)+(l<=y+1&&y+1<=r);
if(d==1)return l<=y&&y<=r;
auto &v=pos[y][f[a][b]];
return upper_bound(v.begin(),v.end(),r)-lower_bound(v.begin(),v.end(),l);
}
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]++;
}
}
for(int i=1;i<=mx;i++)
{
for(int j=1;j<=mx;j++)if(abs(i-j)>=2)pos[j][f[i][j]].push_back(i);
}
int q;
cin>>q;
while(q--)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
int ans=calc(x1,x2,y1,y2)+calc(y1,y2,x1,x2)+calc(x2,x1,y2,y1)+calc(y2,y1,x2,x1);
cout<<ans<<'\n';
}
return 0;
}