题解 P2599 【[ZJOI2009]取石子游戏】

· · 题解

神仙题.jpg。ZJOI是真的神仙。
发现SG函数等东西完全找不到规律,无奈只能翻题解。

首先设L[i][j]表示在[i,j]这一段区间的左侧放上一堆数量为L[i][j]的石子后,先手必败。同理定义R[i][j]表示右侧。

首先我们可以证明L[i][j]唯一,假设存在两个L[i][j],显然较大的那个可以通过一步转移转移到较小的那个,所以不合法。因此L[i][j]唯一。

接下来考虑如何证明L[i][j]一定存在。假设L[i][j]不存在,那么对于这段区间而言,在左边加上任意一堆石子先手都必胜,既然先手必胜意味着先手进行一步操作之后可以到达一个必败态,这里分情况讨论。假设先手拿的是最左边的一堆石子,因为不存在L[i][j],所以只要拿了左边的石子之后,当前局面都是必胜态,所以不可能拿左边的石子。那么只能拿右边的石子,那么无论右边拿了一定量之后,无论左边添加了多少,都是一个必败态,那么此时后手在左侧随便拿走一定数量,这个状态也还是一个必败态,显然也不成立。因此L[i][j]必定存在。

综上,我们知道了L[i][j]一定存在并且唯一,而L[][],R[][]显然是对称的,因此R[i][j]也满足上述性质。

现在考虑如何求解L[i][j],R[i][j]同理。首先边界情况显然,L[i][i]=a[i],因为只剩下两堆一模一样的情况的时候,后手只需要模仿先手的行动对称执行就好了,这样子一定不会输,即先手必败。

接下来来大力分类讨论,为了方便,设L=L[i][j-1],R=R[i][j-1],x=a[j]

R[][]L[][]是对称的,类似的求解即可。

那么最终只需要判断L[2][n]a[1]是否相等即可判断胜负情况。

代码有点丑

#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 1010
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,a[MAX],L[MAX][MAX],R[MAX][MAX];
int main()
{
    int T=read();
    while(T--)
    {
        n=read();
        for(int i=1;i<=n;++i)a[i]=read();
        for(int i=1;i<=n;++i)L[i][i]=R[i][i]=a[i];
        for(int len=2;len<=n;++len)
            for(int i=1,j=i+len-1;j<=n;++i,++j)
            {
                int x=a[j],l=L[i][j-1],r=R[i][j-1];
                if(x==r)L[i][j]=0;
                else if((x>l&&x>r)||(x<l&&x<r))L[i][j]=x;
                else if(r<x&&x<l)L[i][j]=x-1;
                else L[i][j]=x+1;
                x=a[i],l=L[i+1][j],r=R[i+1][j];
                if(x==l)R[i][j]=0;
                else if((x>l&&x>r)||(x<l&&x<r))R[i][j]=x;
                else if(r<x&&x<l)R[i][j]=x+1;
                else R[i][j]=x-1;
            }
        puts(a[1]==L[2][n]?"0":"1");
    }
}