题目链接:https://loj.ac/problem/10248#submit_code
解题思路
神仙题目,迷一般的状态转移,看了yyb大佬的题解,才算明白一点。我们定义L[i][j]表示区间[i,j]左边(即i-1位)加上一堆石子L[i][j],能使先手必败,R[i][j]表示区间[i,j]右边(即j+1位)加上一堆石子R[i][j],能使先手必败。首先,边界L[i][i],R[i][i]肯定是a[i] (Nim博弈)。然后最后只要判断a[1]是否等于L[2][n]就能判断先手必胜还是必败了。
再看状态转移,L[i][j]由L[i][j-1]与R[i][j-1]转移过来,R[i][j]由R[i+1][j],L[i+1][j]转移过来,先看L[i][j],我们设L=L[i][j-1],R[i][j-1],x=a[j]。
- R=x,L[i][j]=0,因为此时已经是必败态了,不必再添了。
- x<L&&x<R,L[i][j]=x,此时两侧石子一致,无论先手怎么去,后手只要模仿先手在另一侧取相同的数量,这样就能保证先手先取完,此时左侧或右侧肯定有一堆没取完,此时可以等价的认为,当前是先手从L或R取了一定数量的石子转移过来的,此时无论后手怎么取都是必胜的。
- L<x<R,L[i][j]=x+1,若先手在左侧取,剩下石子为rest。rest=0,等价于先手在R的状态下,在右侧取了一定数量的石子转移过来。rest<L,后手只要将右侧石头取到与rest相同,就可以转移到第2种情况。rest=L,后手将右侧石子取完,就转移到L。rest>L,后手只要将右手石子取到rest-1即又回到当前情况。若先手在右侧取,当rest=0,将左侧取出L,即可转移到L。当rest<L,同上,取左侧,可以转移到第2种情况。rest>=L,后手通过在左侧或右侧,使得左侧=右侧+1,又回到当前情况。
- R<x<L,L[i][j]=x-1,与第三种情况类似,可以看成互换一下左右两侧。
- 当x>L&&x>R,L[i][j]=x。无论先手怎么取,后手都可以转移到之前和当前的状态。
R[i][j]和L[i][j]是对称的,类似的求解即可。
AC代码
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1e3+5;
int a[maxn],L[maxn][maxn],R[maxn][maxn];
int T,n;
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
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==1)
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");
}
return 0;
}
来源:oschina
链接:https://my.oschina.net/u/4373790/blog/4555853