【JZOJ4381】【GDOI2016模拟3.11】游戏(SG函数+记忆化搜索)

Problem

这里写图片描述

Hint

N,M≤20

Solution

  • 前置技能:SG定理。
  • 首先,这个游戏其实可以分成两个子游戏:对于i+j&1=0的位置是一个;对于i+j&1=1的位置是一个。
  • 譬如,对于一个n*m的矩阵,我们如下划分:
    这里写图片描述
  • 如图,红色为一个子游戏,蓝色为另一个子游戏。
  • 它的正确性就在于:我们肯定不会因为操作了红色格子而影响到蓝色格子;也不会因为操作了蓝色格子而影响到红色格子。

  • 菱形图不太好处理,考虑将整个图转化为正方形。
  • 我们将整个图沿矩阵中心顺时针(或逆时针)旋转45°,将红色、蓝色分别抽出来,得到下图:
    这里写图片描述
  • 拿红色区域来说,新图的(1,2)、(2,1)、(2,2)、(2,3)分别对应原图的(1,1)、(3,1)、(2,2)、(1,3)。旋转方法具体看Code。

  • 这样,原图中的’L’影响的区域应为’\’形,新图则为’|’形;原图中的’R’影响的区域应为’/’形,新图则为’——’形。
  • 考虑使用四个数x1、y1、x2、y2表示一个状态,代表新图中的一个左上角、右下角。这样即可表示新图中的一个矩形。
  • 我们现在要求出SG[1][1][n+m>>1][n+m>>1]。(新图中的矩形[1][1][n+m>>1][n+m>>1]的SG值)

  • 可以暴枚一个点(i,j),我们要操作它。
  • 若点(i,j)的值为’L’,则影响区域为’|’形,即一条x=i的直线。这样可以将当前的状态now分为左右两个子矩形a、b。
  • 若点(i,j)的值为其他,也差不多。
  • 现在,关键是要通过a、b求解SG(now)。

  • 易知状态{a,b}为状态now的一个后继状态。换句话说,先手可以通过一步操作,将状态now变成状态{a,b}。
  • 先考虑一下SG({a,b})的取值。
  • SG({a,b})应为SG(a)^SG(b)。
  • 证明的话,因为a、b是两个互不影响、互不相交的子游戏,所以可以直接套用SG定理。

  • 设to(i)表示状态i的后继状态的集合。那么SG(i)=mex{SG(to(i))}。
  • 因为使用mex的话,先手就可以通过一步操作将SG值变成一个更小的数。这样就类比普通的取石子游戏。

  • 那么,总共有 n2m2 n 2 ∗ m 2 种状态。可以考虑记忆化搜索。

  • 最后,我们求出SG(红色区域)和SG(蓝色区域后),答案即为两者的异或和。
  • 因为红色区域和蓝色区域也是两个互不影响、互不相交的子游戏,所以依然可以直接套用SG定理。

  • 时间复杂度: O(n3m3) O ( n 3 ∗ m 3 )

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define mem(a,x) memset(a,x,sizeof a)
#define clear(a) mem(a,0)
#define init mem(SG,200)

const int N=21;
int i,j,n,m,a[N][N],b[N][N],SG1,SG2,SG[N][N][N][N];
char c[N][N];

int calc(int a[N][N],int x1,int y1,int x2,int y2)
{
    if( x1>n || y1>n || !x2 || !y2 ) return 0;
    if(SG[x1][y1][x2][y2]>=0) return SG[x1][y1][x2][y2];

    int i,j,to;
    bool bz[N<<1]; clear(bz);

    fo(i,x1,x2)
        fo(j,y1,y2)
            if(a[i][j])
            {
                switch(a[i][j])
                {
                    case 76: to=calc(a,x1,y1,x2,j-1)^calc(a,x1,j+1,x2,y2); break;
                    case 82: to=calc(a,x1,y1,i-1,y2)^calc(a,i+1,y1,x2,y2); break;
                    case 88: to=calc(a,x1,y1,i-1,j-1)^calc(a,x1,j+1,i-1,y2)^calc(a,i+1,y1,x2,j-1)^calc(a,i+1,j+1,x2,y2); break;
                }
                bz[to]=1;
            }

    fo(i,0,n+m) if(!bz[i]) return SG[x1][y1][x2][y2]=i;
}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {   
        clear(a); clear(b); 
        fo(i,1,n) scanf("%s",c[i]+1);
        fo(i,1,n)
            fo(j,1,m)
                if(i+j&1)
                        b[i+j>>1][n+1-i+j>>1]=c[i][j];
                else    a[i+j>>1][n+1-i+j>>1]=c[i][j];

        init; 
        SG1=calc(a,1,1,n+m>>1,n+m>>1);

        init; 
        SG2=calc(b,1,1,n+m>>1,n+m>>1);

        printf( SG1^SG2 ? "WIN\n" : "LOSE\n" );
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值